@regionerne/gis-komponent 0.0.4 → 0.0.13
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.
- package/fesm2022/regionerne-gis-komponent.mjs +1113 -89
- package/fesm2022/regionerne-gis-komponent.mjs.map +1 -1
- package/lib/components/active-objects/activeObjects.component.d.ts +28 -0
- package/lib/components/gis-komponent/gis-komponent.component.d.ts +10 -1
- package/lib/components/layer-selector/layer-selector.component.d.ts +17 -5
- package/lib/components/lib-notification/lib-notification.component.d.ts +10 -0
- package/lib/components/map-search/map-search.component.d.ts +11 -0
- package/lib/components/toolbox/toolbox.component.d.ts +73 -0
- package/lib/config/library-provider.d.ts +1 -0
- package/lib/models/GisKomponentSettings.d.ts +14 -4
- package/lib/models/ILayer.d.ts +2 -0
- package/lib/models/IProfile.d.ts +5 -0
- package/lib/models/profile.d.ts +3 -0
- package/lib/services/UndoRedo.service.d.ts +20 -0
- package/lib/services/drawLayerSource.service.d.ts +16 -0
- package/lib/services/featureHelper.service.d.ts +14 -0
- package/lib/services/geometri-split.service.d.ts +17 -0
- package/lib/services/komponentSettingsHelper.service.d.ts +6 -4
- package/lib/services/layerError.service.d.ts +20 -0
- package/lib/services/layerHelper.service.d.ts +6 -3
- package/lib/services/libError.interceptor.service.d.ts +2 -0
- package/lib/services/libError.service.d.ts +10 -0
- package/lib/services/profile.service.d.ts +3 -0
- package/package.json +3 -2
- package/public-api.d.ts +2 -0
|
@@ -1,20 +1,22 @@
|
|
|
1
|
+
import * as i1 from '@angular/common';
|
|
1
2
|
import { CommonModule } from '@angular/common';
|
|
2
3
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { InjectionToken, inject, Injectable, Input, ViewChild
|
|
4
|
+
import { InjectionToken, inject, Injectable, Component, Input, ViewChild } from '@angular/core';
|
|
4
5
|
import * as i2 from '@angular/material/icon';
|
|
5
6
|
import { MatIconModule } from '@angular/material/icon';
|
|
6
|
-
import Map from 'ol/Map';
|
|
7
|
+
import Map$1 from 'ol/Map';
|
|
7
8
|
import View from 'ol/View';
|
|
8
9
|
import TileLayer from 'ol/layer/Tile';
|
|
9
|
-
import
|
|
10
|
+
import VectorLayer from 'ol/layer/Vector';
|
|
10
11
|
import VectorSource from 'ol/source/Vector';
|
|
11
12
|
import ImageWMS from 'ol/source/ImageWMS';
|
|
12
13
|
import WMTSCapabilities from 'ol/format/WMTSCapabilities';
|
|
13
14
|
import Draw from 'ol/interaction/Draw';
|
|
14
15
|
import Translate from 'ol/interaction/Translate';
|
|
15
16
|
import ImageLayer from 'ol/layer/Image';
|
|
16
|
-
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
|
17
|
+
import { HttpClient, HttpErrorResponse, HttpHeaders, provideHttpClient, withInterceptors } from '@angular/common/http';
|
|
17
18
|
import OLLayerGroup from 'ol/layer/Group';
|
|
19
|
+
import * as i4$1 from '@angular/material/select';
|
|
18
20
|
import { MatSelectModule } from '@angular/material/select';
|
|
19
21
|
import * as i2$1 from '@angular/material/list';
|
|
20
22
|
import { MatListModule } from '@angular/material/list';
|
|
@@ -24,23 +26,41 @@ import { register } from 'ol/proj/proj4';
|
|
|
24
26
|
import proj4 from 'proj4';
|
|
25
27
|
import { Control } from 'ol/control';
|
|
26
28
|
import { TileWMS } from 'ol/source';
|
|
27
|
-
import {
|
|
29
|
+
import { Subject, of, tap, map, filter, combineLatest, catchError, throwError } from 'rxjs';
|
|
28
30
|
import WMTS, { optionsFromCapabilities } from 'ol/source/WMTS';
|
|
29
31
|
import * as i4 from '@angular/cdk/drag-drop';
|
|
30
32
|
import { moveItemInArray, transferArrayItem, DragDropModule } from '@angular/cdk/drag-drop';
|
|
31
|
-
import * as
|
|
33
|
+
import * as i3 from '@angular/material/form-field';
|
|
32
34
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
33
|
-
import * as i3 from '@angular/forms';
|
|
35
|
+
import * as i3$1 from '@angular/forms';
|
|
34
36
|
import { FormsModule } from '@angular/forms';
|
|
35
37
|
import * as i5 from '@angular/material/expansion';
|
|
36
38
|
import { MatExpansionModule } from '@angular/material/expansion';
|
|
37
39
|
import * as i6 from '@angular/material/input';
|
|
38
40
|
import { MatInputModule } from '@angular/material/input';
|
|
39
41
|
import ol_control_Control from 'ol/control/Control';
|
|
40
|
-
import
|
|
42
|
+
import TileSource from 'ol/source/Tile';
|
|
43
|
+
import ImageSource from 'ol/source/Image';
|
|
44
|
+
import { unByKey } from 'ol/Observable';
|
|
45
|
+
import { MAT_SNACK_BAR_DATA, MatSnackBarRef, MatSnackBar } from '@angular/material/snack-bar';
|
|
41
46
|
import * as SLDReader from '@nieuwlandgeo/sldreader';
|
|
42
|
-
import { extend } from 'ol/extent';
|
|
43
47
|
import 'ol/ol.css';
|
|
48
|
+
import { LineString, Point } from 'ol/geom';
|
|
49
|
+
import { getArea, getLength } from 'ol/sphere';
|
|
50
|
+
import { Select, Modify } from 'ol/interaction';
|
|
51
|
+
import { Style, Text, Stroke, Fill } from 'ol/style';
|
|
52
|
+
import * as i3$2 from '@angular/material/core';
|
|
53
|
+
import { MatOptionModule } from '@angular/material/core';
|
|
54
|
+
import WKT from 'ol/format/WKT';
|
|
55
|
+
import { buffer as buffer$1 } from 'ol/extent';
|
|
56
|
+
import Feature from 'ol/Feature';
|
|
57
|
+
import { never, always } from 'ol/events/condition';
|
|
58
|
+
import { buffer, featureCollection, difference } from '@turf/turf';
|
|
59
|
+
import GeoJSON from 'ol/format/GeoJSON';
|
|
60
|
+
import * as i6$1 from '@angular/material/tooltip';
|
|
61
|
+
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
62
|
+
import * as i5$1 from '@angular/material/autocomplete';
|
|
63
|
+
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
|
44
64
|
|
|
45
65
|
const GISKOMPONENT_CONFIG = new InjectionToken('GisKomponentConfig');
|
|
46
66
|
|
|
@@ -52,7 +72,15 @@ class ProfileService {
|
|
|
52
72
|
const url = `${this._baseUrl}/api/profile/identifier/${identifier}`;
|
|
53
73
|
return this._http.get(url);
|
|
54
74
|
}
|
|
75
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ProfileService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
76
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ProfileService, providedIn: 'root' });
|
|
55
77
|
}
|
|
78
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ProfileService, decorators: [{
|
|
79
|
+
type: Injectable,
|
|
80
|
+
args: [{
|
|
81
|
+
providedIn: 'root'
|
|
82
|
+
}]
|
|
83
|
+
}] });
|
|
56
84
|
|
|
57
85
|
// logo-control.ts
|
|
58
86
|
class LogoControl extends Control {
|
|
@@ -84,9 +112,6 @@ class CopyrightControl extends Control {
|
|
|
84
112
|
}
|
|
85
113
|
}
|
|
86
114
|
|
|
87
|
-
Injectable({
|
|
88
|
-
providedIn: 'root'
|
|
89
|
-
});
|
|
90
115
|
class LayerHelperService {
|
|
91
116
|
_layerDbIdKey = 'layerDbId';
|
|
92
117
|
_layerGroupDbIdKey = 'layerGroupDbId';
|
|
@@ -106,9 +131,11 @@ class LayerHelperService {
|
|
|
106
131
|
const dbId = layer.get(this._layerGroupDbIdKey) ?? -1;
|
|
107
132
|
return +dbId;
|
|
108
133
|
}
|
|
109
|
-
applyCachedLayersToDisplayInMap(map) {
|
|
110
|
-
const layerIdsCachedToDisplay = localStorage.getItem(this._layerIdsToDisplayInMapKeyName)?.split(',');
|
|
134
|
+
applyCachedLayersToDisplayInMap(map, profileId) {
|
|
135
|
+
const layerIdsCachedToDisplay = localStorage.getItem(`${this._layerIdsToDisplayInMapKeyName}_${profileId}`)?.split(',');
|
|
111
136
|
if (layerIdsCachedToDisplay) {
|
|
137
|
+
//Bottom layers are rendered first, top layers are rendered last (on top)
|
|
138
|
+
layerIdsCachedToDisplay.reverse();
|
|
112
139
|
map.getLayers().getArray().forEach(layergroup => {
|
|
113
140
|
if (layergroup instanceof OLLayerGroup) {
|
|
114
141
|
layergroup.getLayers().getArray().forEach(l => {
|
|
@@ -118,31 +145,179 @@ class LayerHelperService {
|
|
|
118
145
|
l.setVisible(!current);
|
|
119
146
|
}
|
|
120
147
|
}
|
|
148
|
+
else {
|
|
149
|
+
//Set z index to match the order in cache
|
|
150
|
+
l.setZIndex(layerIdsCachedToDisplay.indexOf(l.get(this._layerDbIdKey).toString()));
|
|
151
|
+
//console.log('layer id '+l.get(this._layerDbIdKey)+' - setZIndex:'+layerIdsCachedToDisplay.indexOf(l.get(this._layerDbIdKey).toString()));
|
|
152
|
+
}
|
|
121
153
|
});
|
|
122
154
|
}
|
|
123
155
|
});
|
|
124
156
|
}
|
|
125
157
|
}
|
|
126
|
-
|
|
158
|
+
updateLayerOpacityInMap(map, layer) {
|
|
159
|
+
map.getLayers().getArray().forEach(layergroup => {
|
|
160
|
+
if (layergroup instanceof OLLayerGroup) {
|
|
161
|
+
layergroup.getLayers().getArray().forEach(l => {
|
|
162
|
+
if (l.get(this._layerDbIdKey) === layer.id && layer.opacity) {
|
|
163
|
+
l.setOpacity(layer.opacity);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
toggleLayerInMap(map, layer) {
|
|
127
170
|
map.getLayers().getArray().forEach(layergroup => {
|
|
128
171
|
if (layergroup instanceof OLLayerGroup) {
|
|
129
172
|
layergroup.getLayers().getArray().forEach(l => {
|
|
130
173
|
if (l.get(this._layerDbIdKey) === layer.id) {
|
|
131
174
|
const current = l.getVisible();
|
|
132
175
|
l.setVisible(!current);
|
|
133
|
-
layer.visible = !current;
|
|
134
|
-
//console.log('set '+ layer.id +' to visible '+layer.displayInMap);
|
|
135
|
-
localStorage.setItem(this._mapFilteredLayerGroupsKeyName, JSON.stringify(filteredLayerGroups));
|
|
136
|
-
localStorage.setItem(this._layerIdsToDisplayInMapKeyName, filteredLayerGroups
|
|
137
|
-
.flatMap(lg => lg.layers)
|
|
138
|
-
.filter(lg => lg.visible)
|
|
139
|
-
.map(lg => lg.id).join(','));
|
|
140
176
|
}
|
|
141
177
|
});
|
|
142
178
|
}
|
|
143
179
|
});
|
|
144
180
|
}
|
|
181
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LayerHelperService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
182
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LayerHelperService, providedIn: 'root' });
|
|
183
|
+
}
|
|
184
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LayerHelperService, decorators: [{
|
|
185
|
+
type: Injectable,
|
|
186
|
+
args: [{
|
|
187
|
+
providedIn: 'root'
|
|
188
|
+
}]
|
|
189
|
+
}] });
|
|
190
|
+
|
|
191
|
+
class LibNotificationComponent {
|
|
192
|
+
data = inject(MAT_SNACK_BAR_DATA);
|
|
193
|
+
_snackRef = inject((MatSnackBarRef));
|
|
194
|
+
dismiss() {
|
|
195
|
+
this._snackRef.dismiss();
|
|
196
|
+
}
|
|
197
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LibNotificationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
198
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: LibNotificationComponent, isStandalone: true, selector: "lib-notification", ngImport: i0, template: "<div class=\"notification-container\">\n <button mat-icon-button class=\"close-btn\" (click)=\"dismiss()\">×</button>\n @for (message of data.messages; track message) {\n <div class=\"lib-notification\">\n <span>{{ message }}</span>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:block}.notification-container{display:flex;flex-direction:column;gap:12px;padding:8px;min-width:300px;max-width:480px}.close-btn{position:absolute;top:12px;right:12px;width:32px;height:32px;background:#ffffffe6;border:none;border-radius:50%;color:#666;font-size:20px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s cubic-bezier(.4,0,.2,1);backdrop-filter:blur(8px);z-index:10}.close-btn:hover{background:#fff;color:#333}.lib-notification{position:relative;background:linear-gradient(135deg,#d32f2f,#b71c1c);color:#fff;padding:16px 52px 16px 20px;border-radius:12px;box-shadow:0 4px 20px #d32f2f4d,0 2px 8px #00000026,inset 0 1px #fff3;font-weight:500;font-size:14px;line-height:1.5;border:1px solid rgba(255,255,255,.1);overflow:hidden;backdrop-filter:blur(10px);animation-fill-mode:forwards}.lib-notification:before{content:\"\";position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,#fffc,#fff6);border-radius:12px 12px 0 0;animation:progress 5s linear forwards}.lib-notification:hover{transform:translateY(-1px);box-shadow:0 6px 25px #d32f2f66,0 4px 12px #0000002e,inset 0 1px #fff3;transition:all .2s ease}.lib-notification.success{background:linear-gradient(135deg,#2e7d32,#1b5e20);box-shadow:0 4px 20px #2e7d324d,0 2px 8px #00000026}.lib-notification.success:before{background:linear-gradient(90deg,#fffc,#fff6)}.lib-notification.success:after{content:\"\\2705\"}.lib-notification.warning{background:linear-gradient(135deg,#f57c00,#e65100);box-shadow:0 4px 20px #f57c004d,0 2px 8px #00000026}.lib-notification.warning:before{background:linear-gradient(90deg,#fffc,#fff6)}.lib-notification.info{background:linear-gradient(135deg,#0288d1,#01579b);box-shadow:0 4px 20px #0288d14d,0 2px 8px #00000026}.lib-notification.info:before{background:linear-gradient(90deg,#fffc,#fff6)}.lib-notification.info:after{content:\"\\2139\\fe0f\"}@keyframes slideIn{0%{opacity:0;transform:translate(100%) scale(.9)}to{opacity:1;transform:translate(0) scale(1)}}@keyframes progress{0%{width:100%}to{width:0%}}@media (max-width: 480px){.notification-container{min-width:calc(100vw - 32px);max-width:calc(100vw - 32px);margin:0 16px}.lib-notification{padding:14px 48px 14px 16px;font-size:13px}.close-btn{top:8px;right:8px;width:28px;height:28px;font-size:18px}}\n"] });
|
|
199
|
+
}
|
|
200
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LibNotificationComponent, decorators: [{
|
|
201
|
+
type: Component,
|
|
202
|
+
args: [{ selector: 'lib-notification', standalone: true, template: "<div class=\"notification-container\">\n <button mat-icon-button class=\"close-btn\" (click)=\"dismiss()\">×</button>\n @for (message of data.messages; track message) {\n <div class=\"lib-notification\">\n <span>{{ message }}</span>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:block}.notification-container{display:flex;flex-direction:column;gap:12px;padding:8px;min-width:300px;max-width:480px}.close-btn{position:absolute;top:12px;right:12px;width:32px;height:32px;background:#ffffffe6;border:none;border-radius:50%;color:#666;font-size:20px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s cubic-bezier(.4,0,.2,1);backdrop-filter:blur(8px);z-index:10}.close-btn:hover{background:#fff;color:#333}.lib-notification{position:relative;background:linear-gradient(135deg,#d32f2f,#b71c1c);color:#fff;padding:16px 52px 16px 20px;border-radius:12px;box-shadow:0 4px 20px #d32f2f4d,0 2px 8px #00000026,inset 0 1px #fff3;font-weight:500;font-size:14px;line-height:1.5;border:1px solid rgba(255,255,255,.1);overflow:hidden;backdrop-filter:blur(10px);animation-fill-mode:forwards}.lib-notification:before{content:\"\";position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,#fffc,#fff6);border-radius:12px 12px 0 0;animation:progress 5s linear forwards}.lib-notification:hover{transform:translateY(-1px);box-shadow:0 6px 25px #d32f2f66,0 4px 12px #0000002e,inset 0 1px #fff3;transition:all .2s ease}.lib-notification.success{background:linear-gradient(135deg,#2e7d32,#1b5e20);box-shadow:0 4px 20px #2e7d324d,0 2px 8px #00000026}.lib-notification.success:before{background:linear-gradient(90deg,#fffc,#fff6)}.lib-notification.success:after{content:\"\\2705\"}.lib-notification.warning{background:linear-gradient(135deg,#f57c00,#e65100);box-shadow:0 4px 20px #f57c004d,0 2px 8px #00000026}.lib-notification.warning:before{background:linear-gradient(90deg,#fffc,#fff6)}.lib-notification.info{background:linear-gradient(135deg,#0288d1,#01579b);box-shadow:0 4px 20px #0288d14d,0 2px 8px #00000026}.lib-notification.info:before{background:linear-gradient(90deg,#fffc,#fff6)}.lib-notification.info:after{content:\"\\2139\\fe0f\"}@keyframes slideIn{0%{opacity:0;transform:translate(100%) scale(.9)}to{opacity:1;transform:translate(0) scale(1)}}@keyframes progress{0%{width:100%}to{width:0%}}@media (max-width: 480px){.notification-container{min-width:calc(100vw - 32px);max-width:calc(100vw - 32px);margin:0 16px}.lib-notification{padding:14px 48px 14px 16px;font-size:13px}.close-btn{top:8px;right:8px;width:28px;height:28px;font-size:18px}}\n"] }]
|
|
203
|
+
}] });
|
|
204
|
+
|
|
205
|
+
class LibErrorService {
|
|
206
|
+
_snackBar = inject(MatSnackBar);
|
|
207
|
+
_messages = [];
|
|
208
|
+
//Format errors from backend or UI
|
|
209
|
+
format(error) {
|
|
210
|
+
if (error instanceof HttpErrorResponse) {
|
|
211
|
+
if (error.error?.message)
|
|
212
|
+
return error.error.message;
|
|
213
|
+
if (typeof error.error === 'string')
|
|
214
|
+
return error.error;
|
|
215
|
+
return `Server returned ${error.status}`;
|
|
216
|
+
}
|
|
217
|
+
if (error instanceof Error)
|
|
218
|
+
return error.message;
|
|
219
|
+
return 'An unexpected error occurred.';
|
|
220
|
+
}
|
|
221
|
+
// Log errors (for debugging or telemetry)
|
|
222
|
+
log(error) {
|
|
223
|
+
console.error('[Library Error]', error);
|
|
224
|
+
}
|
|
225
|
+
// Show user-friendly message via Material snackbar
|
|
226
|
+
show(error) {
|
|
227
|
+
const message = this.format(error);
|
|
228
|
+
// Prevent duplicates
|
|
229
|
+
if (this._messages.includes(message))
|
|
230
|
+
return;
|
|
231
|
+
this._messages.push(message);
|
|
232
|
+
const ref = this._snackBar.openFromComponent(LibNotificationComponent, {
|
|
233
|
+
data: { messages: this._messages },
|
|
234
|
+
duration: 5000,
|
|
235
|
+
horizontalPosition: 'right',
|
|
236
|
+
verticalPosition: 'top',
|
|
237
|
+
politeness: 'off',
|
|
238
|
+
panelClass: ['lib-notification-container'],
|
|
239
|
+
});
|
|
240
|
+
// Remove from active stack when it closes
|
|
241
|
+
ref.afterDismissed().subscribe(() => {
|
|
242
|
+
this._messages = this._messages.filter(m => m !== message);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LibErrorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
246
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LibErrorService, providedIn: 'root' });
|
|
145
247
|
}
|
|
248
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LibErrorService, decorators: [{
|
|
249
|
+
type: Injectable,
|
|
250
|
+
args: [{ providedIn: 'root' }]
|
|
251
|
+
}] });
|
|
252
|
+
|
|
253
|
+
class LayerErrorService {
|
|
254
|
+
layerStatusChanged = new Subject();
|
|
255
|
+
layerStatuses = new Map();
|
|
256
|
+
listeners = new Map();
|
|
257
|
+
_errorService = inject(LibErrorService);
|
|
258
|
+
registerLayer(layerId, layer) {
|
|
259
|
+
// BaseLayer does not guarantee getSource(), so we must narrow type first
|
|
260
|
+
let source;
|
|
261
|
+
if (layer instanceof TileLayer || layer instanceof ImageLayer || layer instanceof VectorLayer) {
|
|
262
|
+
source = layer.getSource();
|
|
263
|
+
}
|
|
264
|
+
if (!source) {
|
|
265
|
+
console.warn(`[Layer ${layerId}] has no source; skipping error tracking.`);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const markError = (event) => {
|
|
269
|
+
this.layerStatuses.set(layerId, { layerId, hasError: true, lastError: event });
|
|
270
|
+
console.error(`[Layer ${layerId}] load error:`, event);
|
|
271
|
+
this._errorService.show(new Error(`Indlæsningsfejl for lag med id ${layerId}.`));
|
|
272
|
+
this.layerStatusChanged.next(layerId);
|
|
273
|
+
};
|
|
274
|
+
const clearError = () => {
|
|
275
|
+
this.layerStatuses.set(layerId, { layerId, hasError: false });
|
|
276
|
+
this.layerStatusChanged.next(layerId);
|
|
277
|
+
};
|
|
278
|
+
let keys = [];
|
|
279
|
+
if (source instanceof TileSource) {
|
|
280
|
+
keys.push(source.on('tileloaderror', markError));
|
|
281
|
+
keys.push(source.on('tileloadend', clearError));
|
|
282
|
+
}
|
|
283
|
+
else if (source instanceof ImageSource) {
|
|
284
|
+
keys.push(source.on('imageloaderror', markError));
|
|
285
|
+
keys.push(source.on('imageloadend', clearError));
|
|
286
|
+
}
|
|
287
|
+
else if (source instanceof VectorSource) {
|
|
288
|
+
keys.push(source.on('featuresloaderror', markError));
|
|
289
|
+
keys.push(source.on('featuresloadend', clearError));
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
console.warn(`[Layer ${layerId}] Unknown source type; cannot attach error tracking.`);
|
|
293
|
+
}
|
|
294
|
+
if (keys.length) {
|
|
295
|
+
this.listeners.set(layerId, keys);
|
|
296
|
+
}
|
|
297
|
+
this.layerStatusChanged.next(layerId);
|
|
298
|
+
}
|
|
299
|
+
unregisterLayer(layerId) {
|
|
300
|
+
const keys = this.listeners.get(layerId);
|
|
301
|
+
if (keys) {
|
|
302
|
+
keys.forEach(k => unByKey(k));
|
|
303
|
+
this.listeners.delete(layerId);
|
|
304
|
+
}
|
|
305
|
+
this.layerStatuses.delete(layerId);
|
|
306
|
+
this.layerStatusChanged.next(layerId);
|
|
307
|
+
}
|
|
308
|
+
getLayerStatus(layerId) {
|
|
309
|
+
return this.layerStatuses.get(layerId);
|
|
310
|
+
}
|
|
311
|
+
getAllStatuses() {
|
|
312
|
+
return Array.from(this.layerStatuses.values());
|
|
313
|
+
}
|
|
314
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LayerErrorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
315
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LayerErrorService, providedIn: 'root' });
|
|
316
|
+
}
|
|
317
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LayerErrorService, decorators: [{
|
|
318
|
+
type: Injectable,
|
|
319
|
+
args: [{ providedIn: 'root' }]
|
|
320
|
+
}] });
|
|
146
321
|
|
|
147
322
|
class LayerSelectorComponent {
|
|
148
323
|
set contentIcon(content) {
|
|
@@ -153,10 +328,12 @@ class LayerSelectorComponent {
|
|
|
153
328
|
}
|
|
154
329
|
map;
|
|
155
330
|
profile;
|
|
331
|
+
currentZoomLevel;
|
|
156
332
|
searchText = '';
|
|
157
333
|
allLayerGroups = [];
|
|
158
334
|
filteredLayerGroups = [];
|
|
159
335
|
showSelector = false;
|
|
336
|
+
_namesToGoLast = ['Baggrundskort'];
|
|
160
337
|
_layerSelectorIcon;
|
|
161
338
|
_layerSelectorIconControl;
|
|
162
339
|
_layerSelectorBody;
|
|
@@ -164,38 +341,92 @@ class LayerSelectorComponent {
|
|
|
164
341
|
_mapFilteredLayerGroupsKeyName = 'mapFilteredLayerGroups';
|
|
165
342
|
_layerIdsToDisplayInMapKeyName = 'layerIdsToDisplayInMap';
|
|
166
343
|
_layerHelper = inject(LayerHelperService);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
344
|
+
_layerErrorService = inject(LayerErrorService);
|
|
345
|
+
ngOnInit() {
|
|
346
|
+
this._layerErrorService.layerStatusChanged
|
|
347
|
+
.subscribe(layerId => {
|
|
348
|
+
const layerHasErrors = this._layerErrorService.getLayerStatus(layerId)?.hasError;
|
|
349
|
+
if (layerHasErrors != undefined)
|
|
350
|
+
this.setLayerErrorStatus(layerId, layerHasErrors);
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
ngOnChanges(changes) {
|
|
354
|
+
if (changes['profile'] && this.profile) {
|
|
170
355
|
this.allLayerGroups = this.profile.layerGroups
|
|
171
|
-
.map((lg, idx) => ({ ...lg,
|
|
172
|
-
|
|
356
|
+
.map((lg, idx) => ({ ...lg,
|
|
357
|
+
noOfVisibleLayers: lg.layers.filter(l => l.visible).length,
|
|
358
|
+
visible: lg.layers.some(l => l.visible),
|
|
359
|
+
expanded: lg.layers.some(l => l.visible),
|
|
360
|
+
layers: lg.layers.filter((l) => l.activeInSelector),
|
|
173
361
|
sortOrder: idx }))
|
|
174
362
|
.filter(g => g.layers.length > 0);
|
|
175
|
-
const
|
|
176
|
-
if (
|
|
177
|
-
const
|
|
178
|
-
if (
|
|
179
|
-
//
|
|
363
|
+
const cacheValue = localStorage.getItem(`${this._mapFilteredLayerGroupsKeyName}_${this.profile.id}`);
|
|
364
|
+
if (cacheValue) {
|
|
365
|
+
const cachedProfileInfo = JSON.parse(cacheValue);
|
|
366
|
+
if (cachedProfileInfo.profile.versionId < this.profile.versionId) {
|
|
367
|
+
//Profile version changed in backend, use that
|
|
180
368
|
this.filteredLayerGroups = this.setfilteredGroups();
|
|
369
|
+
this._cacheProfileInfo();
|
|
181
370
|
}
|
|
182
371
|
else {
|
|
183
|
-
this.filteredLayerGroups =
|
|
184
|
-
this._setVisibleLayersFromCache(
|
|
372
|
+
this.filteredLayerGroups = cachedProfileInfo.cachedLayerGroups;
|
|
373
|
+
this._setVisibleLayersFromCache(cachedProfileInfo.cachedLayerGroups);
|
|
185
374
|
}
|
|
186
375
|
}
|
|
187
376
|
else {
|
|
188
377
|
this.filteredLayerGroups = this.setfilteredGroups();
|
|
378
|
+
this._cacheProfileInfo();
|
|
189
379
|
}
|
|
190
|
-
localStorage.setItem(this._layerIdsToDisplayInMapKeyName, this.filteredLayerGroups
|
|
191
|
-
.flatMap(lg => lg.layers)
|
|
192
|
-
.filter(lg => lg.visible)
|
|
193
|
-
.map(lg => lg.id).join(','));
|
|
194
380
|
this._initializeMapIconControl();
|
|
195
381
|
}
|
|
196
382
|
}
|
|
197
383
|
toggleLayer(layer) {
|
|
198
|
-
this._layerHelper.
|
|
384
|
+
this._layerHelper.toggleLayerInMap(this.map, layer);
|
|
385
|
+
// Toggle layer in all groups in the selector UI
|
|
386
|
+
this.filteredLayerGroups.forEach(group => {
|
|
387
|
+
group.layers.forEach(l => {
|
|
388
|
+
if (layer.id === l.id) {
|
|
389
|
+
l.visible = !l.visible;
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
group.noOfVisibleLayers = group.layers.filter(l => l.visible).length;
|
|
393
|
+
group.visible = group.layers.some(l => l.visible);
|
|
394
|
+
group.expanded = group.layers.some(l => l.visible);
|
|
395
|
+
});
|
|
396
|
+
this._cacheProfileInfo();
|
|
397
|
+
}
|
|
398
|
+
setLayerErrorStatus(layerId, errorStatus) {
|
|
399
|
+
this.filteredLayerGroups.forEach(group => {
|
|
400
|
+
group.layers.forEach(l => {
|
|
401
|
+
if (layerId === l.id) {
|
|
402
|
+
l.hasErrors = errorStatus;
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
toggleGroup(event, layerGroup) {
|
|
408
|
+
event.stopPropagation(); // Prevent the panel from expanding/collapsing
|
|
409
|
+
const visible = !layerGroup.visible;
|
|
410
|
+
layerGroup.layers.forEach(layer => {
|
|
411
|
+
if (layer.visible != visible) {
|
|
412
|
+
//Toggle layer in map
|
|
413
|
+
this._layerHelper.toggleLayerInMap(this.map, layer);
|
|
414
|
+
// Toggle layer in all groups in the selector UI
|
|
415
|
+
this.filteredLayerGroups.forEach(group => {
|
|
416
|
+
group.layers.forEach(l => {
|
|
417
|
+
if (layer.id === l.id) {
|
|
418
|
+
l.visible = !l.visible;
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
group.noOfVisibleLayers = group.layers.filter(l => l.visible).length;
|
|
422
|
+
group.visible = group.layers.some(l => l.visible);
|
|
423
|
+
group.expanded = group.layers.some(l => l.visible);
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
layerGroup.visible = visible;
|
|
428
|
+
layerGroup.expanded = layerGroup.layers.some(l => l.visible);
|
|
429
|
+
this._cacheProfileInfo();
|
|
199
430
|
}
|
|
200
431
|
toggleLayerSelector() {
|
|
201
432
|
this.showSelector = !this.showSelector;
|
|
@@ -206,28 +437,44 @@ class LayerSelectorComponent {
|
|
|
206
437
|
this._removeMapBodyControl();
|
|
207
438
|
}
|
|
208
439
|
}
|
|
440
|
+
updateOpacity(layer) {
|
|
441
|
+
this._layerHelper.updateLayerOpacityInMap(this.map, layer);
|
|
442
|
+
}
|
|
443
|
+
stopDrag(event) {
|
|
444
|
+
event.stopPropagation(); // prevents drag container from activating
|
|
445
|
+
}
|
|
209
446
|
setfilteredGroups() {
|
|
210
447
|
const text = this.searchText.toLowerCase();
|
|
211
448
|
return this.allLayerGroups
|
|
212
449
|
.map((g, idx) => {
|
|
213
450
|
if (!text) {
|
|
214
|
-
return { ...g, expanded: false, sortOrder: idx
|
|
451
|
+
return { ...g, expanded: false, sortOrder: idx,
|
|
452
|
+
layers: g.layers.map(layer => ({ ...layer, opacity: 1 }))
|
|
453
|
+
}; // collapsed by default
|
|
215
454
|
}
|
|
216
455
|
const filteredItems = g.layers.filter(l => l.name.toLowerCase().includes(text));
|
|
217
456
|
return {
|
|
218
457
|
...g,
|
|
219
458
|
expanded: filteredItems.length > 0,
|
|
220
459
|
sortOrder: idx,
|
|
221
|
-
layers: filteredItems
|
|
460
|
+
layers: filteredItems.map(layer => ({ ...layer, opacity: 1 }))
|
|
222
461
|
};
|
|
223
462
|
})
|
|
224
463
|
.filter(g => g.layers.length > 0)
|
|
225
|
-
.sort((a, b) =>
|
|
464
|
+
.sort((a, b) => {
|
|
465
|
+
const aIsLast = this._namesToGoLast.includes(a.name) || a.layers.some(layer => layer.background);
|
|
466
|
+
const bIsLast = this._namesToGoLast.includes(b.name) || b.layers.some(layer => layer.background);
|
|
467
|
+
if (aIsLast && !bIsLast)
|
|
468
|
+
return 1;
|
|
469
|
+
if (!aIsLast && bIsLast)
|
|
470
|
+
return -1;
|
|
471
|
+
return a.sortOrder - b.sortOrder;
|
|
472
|
+
});
|
|
226
473
|
}
|
|
227
474
|
dropGroup(event) {
|
|
228
475
|
moveItemInArray(this.filteredLayerGroups, event.previousIndex, event.currentIndex);
|
|
229
476
|
this.updateGroupSortOrders();
|
|
230
|
-
|
|
477
|
+
this._cacheProfileInfo();
|
|
231
478
|
}
|
|
232
479
|
dropLayer(event, group) {
|
|
233
480
|
if (event.previousContainer === event.container) {
|
|
@@ -237,11 +484,12 @@ class LayerSelectorComponent {
|
|
|
237
484
|
transferArrayItem(event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex);
|
|
238
485
|
}
|
|
239
486
|
this.updateLayerSortOrders(group);
|
|
240
|
-
|
|
487
|
+
this._cacheProfileInfo();
|
|
241
488
|
}
|
|
242
489
|
clearSearchText() {
|
|
243
490
|
this.searchText = '';
|
|
244
491
|
this.filteredLayerGroups = this.setfilteredGroups();
|
|
492
|
+
this._cacheProfileInfo();
|
|
245
493
|
}
|
|
246
494
|
updateGroupSortOrders() {
|
|
247
495
|
this.filteredLayerGroups.forEach((g, idx) => (g.sortOrder = idx));
|
|
@@ -249,6 +497,16 @@ class LayerSelectorComponent {
|
|
|
249
497
|
updateLayerSortOrders(group) {
|
|
250
498
|
group.layers.forEach((layer, idx) => (layer.sortOrder = idx));
|
|
251
499
|
}
|
|
500
|
+
_cacheProfileInfo() {
|
|
501
|
+
const cacheItem = { profile: this.profile, cachedLayerGroups: this.filteredLayerGroups };
|
|
502
|
+
localStorage.setItem(`${this._mapFilteredLayerGroupsKeyName}_${this.profile.id}`, JSON.stringify(cacheItem));
|
|
503
|
+
localStorage.setItem(`${this._layerIdsToDisplayInMapKeyName}_${this.profile.id}`, this.filteredLayerGroups
|
|
504
|
+
.flatMap(lg => lg.layers)
|
|
505
|
+
.filter(lg => lg.visible)
|
|
506
|
+
.map(lg => lg.id).join(','));
|
|
507
|
+
// Reflect new order in map
|
|
508
|
+
this._layerHelper.applyCachedLayersToDisplayInMap(this.map, this.profile.id);
|
|
509
|
+
}
|
|
252
510
|
_initializeMapIconControl() {
|
|
253
511
|
this.map.removeControl(this._layerSelectorIconControl);
|
|
254
512
|
const element = this._layerSelectorIcon.nativeElement;
|
|
@@ -271,12 +529,12 @@ class LayerSelectorComponent {
|
|
|
271
529
|
}));
|
|
272
530
|
}
|
|
273
531
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LayerSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
274
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: LayerSelectorComponent, isStandalone: true, selector: "lib-layer-selector", inputs: { map: "map", profile: "profile" }, viewQueries: [{ propertyName: "contentIcon", first: true, predicate: ["layerSelectorIcon"], descendants: true }, { propertyName: "contentBody", first: true, predicate: ["layerSelectorBody"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div #layerSelectorIcon class=\"ol-unselectable ol-control layer-selector-icon\">\n <mat-icon (click)=\"toggleLayerSelector()\">layers</mat-icon>\n</div>\n<div #layerSelectorBody [class.display-none]=\"!showSelector\" class=\"ol-unselectable ol-control layer-selector-body\">\n <div class=\"search-section\">\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Filtrer</mat-label>\n <input \n matInput \n type=\"text\" \n [(ngModel)]=\"searchText\" \n (ngModelChange)=\"filteredLayerGroups=setfilteredGroups()\"\n placeholder=\"Skriv for at filtrere...\"\n />\n </mat-form-field>\n <mat-icon (click)=\"clearSearchText()\">undo</mat-icon>\n </div>\n \n <div\n cdkDropList\n [cdkDropListData]=\"filteredLayerGroups\"\n (cdkDropListDropped)=\"dropGroup($event)\"\n class=\"item-list\">\n @for (group of filteredLayerGroups; track group.id; let gIndex = $index) {\n <div class=\"group\" cdkDrag cdkDragPreviewDisabled>\n <mat-expansion-panel [(expanded)]=\"group.expanded\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n @if (group.expanded) {\n <mat-icon>arrow_upward</mat-icon>\n }\n @if (!group.expanded) {\n <mat-icon>arrow_downward</mat-icon> \n }\n {{ group.name }}\n </mat-panel-title>\n </mat-expansion-panel-header>\n <!-- This is only shown during drag -->\n <!-- <div *cdkDragPreview class=\"drag-preview\">\n {{ group.name }}\n </div> -->\n\n <!-- Placeholder to avoid jump -->\n <!-- <div *cdkDragPlaceholder class=\"drag-placeholder\"></div> -->\n\n <div\n cdkDropList\n [cdkDropListData]=\"group.layers\"\n (cdkDropListDropped)=\"dropLayer($event, group)\"\n class=\"item-list\">\n @for (layer of group.layers; track layer.id; let iIndex = $index) {\n <div class=\"item\" cdkDrag cdkDragPreviewDisabled>\n <mat-icon class=\"drag-indicator\">drag_indicator</mat-icon>\n <span>{{ layer.name }}</span>\n @if (layer.visible) {\n <mat-icon (click)=\"toggleLayer(layer)\" class=\"power-on\">power</mat-icon>(t\u00E6nd)\n }\n @if (!layer.visible) {\n <mat-icon (click)=\"toggleLayer(layer)\" class=\"power-off\">power</mat-icon>(sluk)\n }\n </div>\n }\n </div>\n </mat-expansion-panel>\n </div>\n }\n </div>\n</div>\n", styles: ["::ng-deep .layer-selector-icon{position:absolute;left:auto;right:4em;bottom:.5em;z-index:1000}::ng-deep .layer-selector-icon mat-icon{display:flex;justify-content:center;align-items:center;cursor:pointer;transition:all .3s ease;font-size:45px;width:50px;height:50px;background-color:#0004;border:1px solid rgba(255,255,255,.8);border-radius:8px;-webkit-text-fill-color:#334155;-webkit-text-stroke-width:.9px;-webkit-text-stroke-color:white}::ng-deep .layer-selector-icon mat-icon:hover{box-shadow:0 4px 12px #0003;color:#3e4b5e}::ng-deep .layer-selector-body{position:absolute;left:auto;right:4em;bottom:4em;z-index:1000;background:#fff;border-radius:8px;box-shadow:0 4px 20px #00000026;border:1px solid #e0e0e0;width:320px;max-height:500px;overflow:hidden;display:flex;flex-direction:column}::ng-deep .layer-selector-body .search-section{display:flex;align-items:center;gap:8px;padding:16px 16px 8px;background:#fff;border-bottom:1px solid #e0e0e0}::ng-deep .layer-selector-body .search-section mat-form-field{flex:1}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-outline{background:#fff}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-wrapper{padding-bottom:0}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-outline-thick{color:#1976d2}::ng-deep .layer-selector-body .search-section mat-form-field input{font-size:14px;padding:8px 0}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-label{color:#666;font-weight:500}::ng-deep .layer-selector-body .search-section mat-icon{color:#666;cursor:pointer;padding:8px;border-radius:4px;transition:all .2s ease;font-size:30px;width:20px;height:20px;display:flex;justify-content:center;align-items:end}::ng-deep .layer-selector-body .search-section mat-icon:hover{color:#334155}::ng-deep .layer-selector-body .item-list{flex:1;overflow-y:auto;padding:8px;max-height:400px}::ng-deep .layer-selector-body .item-list .group{margin-bottom:8px;border-radius:6px;overflow:hidden;box-shadow:0 -2px 2px #4868b20a,0 2px 2px #6a6f7517,0 1px 2px #4868b214}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel{box-shadow:none!important;border-radius:0!important}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header{padding:0 16px;height:48px;background:#f8f9fa;border-bottom:1px solid #e0e0e0}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header:hover{background:#e6e7ec}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title{align-items:center;gap:8px;font-weight:600;color:#333;font-size:14px}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title mat-icon{color:#666;font-size:18px;width:18px;height:18px;transition:transform .2s ease}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel .mat-expansion-panel-content{background:#fff}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel .mat-expansion-panel-content .mat-expansion-panel-body{padding:8px 0}::ng-deep .layer-selector-body .item-list .group .item-list{padding:0;max-height:none}::ng-deep .layer-selector-body .item-list .group .item-list .item{display:flex;align-items:center;gap:8px;padding:12px 16px;border-bottom:1px solid #f5f5f5;background:#fff;transition:all .2s ease;cursor:move}::ng-deep .layer-selector-body .item-list .group .item-list .item:last-child{border-bottom:none}::ng-deep .layer-selector-body .item-list .group .item-list .item:hover{background:#f8f9fa}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-preview{background:#fff;box-shadow:0 4px 12px #00000026;border-radius:4px}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-placeholder{background:#e3f2fd;opacity:.6}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.drag-indicator{color:#9e9e9e;font-size:18px;width:18px;height:18px;cursor:move}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-on{color:#4caf50;font-size:20px;width:20px;height:20px;cursor:pointer;margin-left:auto;padding:4px;border-radius:4px}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-on:hover{background:#e8f5e8}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-off{color:#f44336;font-size:20px;width:20px;height:20px;cursor:pointer;margin-left:auto;padding:4px;border-radius:4px}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-off:hover{background:#ffebee}::ng-deep .layer-selector-body .item-list .group .item-list .item>:nth-child(2){flex:1;font-size:14px;color:#333;margin:0}.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:0 5px 15px #00000026;background:#fff;padding:12px 16px}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.item-list.cdk-drop-list-dragging .item:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}.power-on{color:#4caf50}.power-off{color:#f44336}.display-none{display:none}::ng-deep .layer-selector-body .item-list{scrollbar-width:thin;scrollbar-color:#c1c1c1 transparent}\n"], dependencies: [{ kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i1.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.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: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i4.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i4.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i5.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i5.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i5.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i6.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }] });
|
|
532
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: LayerSelectorComponent, isStandalone: true, selector: "lib-layer-selector", inputs: { map: "map", profile: "profile", currentZoomLevel: "currentZoomLevel" }, viewQueries: [{ propertyName: "contentIcon", first: true, predicate: ["layerSelectorIcon"], descendants: true }, { propertyName: "contentBody", first: true, predicate: ["layerSelectorBody"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div #layerSelectorIcon class=\"ol-unselectable ol-control layer-selector-icon\">\n <mat-icon (click)=\"toggleLayerSelector()\">layers</mat-icon>\n</div>\n<div #layerSelectorBody [class.display-none]=\"!showSelector\" class=\"layer-selector-body-wrapper\" cdkDrag cdkDragBoundary=\".map-container\">\n <div class=\"drag-handle-selector\" cdkDragHandle>\n <mat-icon>drag_indicator</mat-icon>\n </div>\n <div class=\"ol-unselectable ol-control layer-selector-body\">\n <div class=\"search-section\">\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Filtrer</mat-label>\n <input \n matInput \n type=\"text\" \n [(ngModel)]=\"searchText\" \n (ngModelChange)=\"filteredLayerGroups=setfilteredGroups()\"\n placeholder=\"Skriv for at filtrere...\"\n />\n </mat-form-field>\n <mat-icon (click)=\"clearSearchText()\">undo</mat-icon>\n </div>\n \n <div\n cdkDropList\n [cdkDropListData]=\"filteredLayerGroups\"\n (cdkDropListDropped)=\"dropGroup($event)\"\n class=\"item-list\">\n @for (group of filteredLayerGroups; track group.id; let gIndex = $index) {\n <div class=\"group\" cdkDrag cdkDragPreviewDisabled>\n <mat-expansion-panel [(expanded)]=\"group.expanded\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n @if (group.expanded) {\n <mat-icon>arrow_upward</mat-icon>\n }\n @if (!group.expanded) {\n <mat-icon>arrow_downward</mat-icon> \n }\n {{ group.name }} \n <mat-icon class=\"lightbulb\">lightbulb</mat-icon>\n ({{ group.noOfVisibleLayers }}/{{ group.layers.length }})\n @if (group.visible) {\n <mat-icon (click)=\"toggleGroup($event, group)\" class=\"power-on\">power</mat-icon>(t\u00E6nd)\n }\n @if (!group.visible) {\n <mat-icon (click)=\"toggleGroup($event, group)\" class=\"power-off\">power_off</mat-icon>(sluk)\n }\n </mat-panel-title>\n </mat-expansion-panel-header>\n <!-- This is only shown during drag -->\n <!-- <div *cdkDragPreview class=\"drag-preview\">\n {{ group.name }}\n </div> -->\n\n <!-- Placeholder to avoid jump -->\n <!-- <div *cdkDragPlaceholder class=\"drag-placeholder\"></div> -->\n\n <div\n cdkDropList\n [cdkDropListData]=\"group.layers\"\n (cdkDropListDropped)=\"dropLayer($event, group)\"\n class=\"item-list\">\n @for (layer of group.layers; track layer.id; let iIndex = $index) {\n <div class=\"item\" cdkDrag cdkDragPreviewDisabled>\n <mat-icon class=\"drag-indicator\">drag_indicator</mat-icon>\n <span>{{ layer.name }}</span>\n @if (layer.maxZoom < currentZoomLevel || layer.minZoom > currentZoomLevel) {\n <mat-icon class=\"zoom-off\">browser_not_supported</mat-icon>(zoom)\n }\n @if (layer.hasErrors) {\n <mat-icon class=\"zoom-off\">wifi_off</mat-icon>(fejle)\n }\n @if (layer.visible) {\n <mat-icon (click)=\"toggleLayer(layer)\" class=\"power-on\">power</mat-icon>(t\u00E6nd)\n }\n @if (!layer.visible) {\n <mat-icon (click)=\"toggleLayer(layer)\" class=\"power-off\">power_off</mat-icon>(sluk)\n }\n <input \n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.05\"\n [(ngModel)]=\"layer.opacity\"\n (input)=\"updateOpacity(layer)\"\n (mousedown)=\"stopDrag($event)\"\n (touchstart)=\"stopDrag($event)\"\n (pointerdown)=\"stopDrag($event)\"\n >\n </div>\n }\n </div>\n </mat-expansion-panel>\n </div>\n }\n </div>\n </div>\n</div>", styles: ["::ng-deep .layer-selector-icon{position:absolute;left:auto;right:4em;bottom:.5em;z-index:1000}::ng-deep .layer-selector-icon mat-icon{display:flex;justify-content:center;align-items:center;cursor:pointer;transition:all .3s ease;outline:none;border:0;height:40px;border-radius:5px;width:40px;background:#fff;box-shadow:#0000004d 0 1px 4px -1px;font-size:2em;color:#666}::ng-deep .layer-selector-icon mat-icon:hover{box-shadow:0 4px 12px #0003;color:#3e4b5e}.layer-selector-body-wrapper{position:absolute;left:auto;right:4em;bottom:4em;z-index:1000;cursor:grab}.layer-selector-body-wrapper.cdk-drag-dragging{opacity:.8;cursor:grab;z-index:1001}.layer-selector-body-wrapper .ol-control{border-radius:0}.drag-handle-selector{display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,#000 40%,transparent);border-radius:4px 4px 0 0;padding:4px;cursor:grab;box-shadow:0 -2px 8px #0003}.drag-handle-selector mat-icon{color:#fff;font-size:20px;width:20px;height:20px}::ng-deep .layer-selector-body{position:relative;left:auto;right:auto;bottom:auto;z-index:auto;background:color-mix(in srgb,#000 40%,transparent);box-shadow:0 4px 20px #00000026;width:480px;max-height:500px;overflow:hidden;display:flex;flex-direction:column}::ng-deep .layer-selector-body .search-section{display:flex;align-items:center;gap:6px;padding:8px 12px 6px}::ng-deep .layer-selector-body .search-section mat-form-field{flex:1}::ng-deep .layer-selector-body .search-section mat-form-field .mat-mdc-text-field-wrapper{padding-bottom:0}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-wrapper{padding-bottom:0;margin-bottom:0}::ng-deep .layer-selector-body .search-section mat-form-field .mat-mdc-form-field-infix{padding-bottom:6px;min-height:auto}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-outline{background:#fff}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-outline-thick{color:#1976d2}::ng-deep .layer-selector-body .search-section mat-form-field input{font-size:13px;padding:3px 0}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-label{color:#fff;font-weight:500;font-size:13px}::ng-deep .layer-selector-body .search-section mat-form-field .mat-mdc-form-field-subscript-wrapper{height:0;margin-top:0}::ng-deep .layer-selector-body .search-section mat-icon{color:#fff;cursor:pointer;padding:6px;border-radius:4px;transition:all .2s ease;font-size:24px;width:24px;height:24px;display:flex;justify-content:center;align-items:center}::ng-deep .layer-selector-body .search-section mat-icon:hover{color:#f9fafb}::ng-deep .layer-selector-body .item-list{flex:1;overflow-y:auto;padding:6px;max-height:400px}::ng-deep .layer-selector-body .item-list .group{margin-bottom:6px;overflow:hidden;box-shadow:0 -2px 2px #4868b20a,0 2px 2px #6a6f7517,0 1px 2px #4868b214}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel{box-shadow:none!important;border-radius:0!important}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header{padding:0 12px;height:40px;background:color-mix(in srgb,#000 55%,transparent)}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header:hover{background:color-mix(in srgb,#000 60%,transparent)!important}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title{align-items:center;gap:6px;font-weight:600;color:#fff;font-size:13px}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title mat-icon{color:#fff;font-size:16px;width:16px;height:16px;transition:transform .2s ease}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title mat-icon.lightbulb{color:#dfca0e}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title mat-icon.power-on{color:#4caf50}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title mat-icon.power-off{color:#f44336}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel .mat-expansion-panel-content{background:color-mix(in srgb,#000 40%,transparent)}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel .mat-expansion-panel-content .mat-expansion-panel-body{padding:4px 0}::ng-deep .layer-selector-body .item-list .group .item-list{padding:0;max-height:none}::ng-deep .layer-selector-body .item-list .group .item-list .item{display:flex;align-items:center;gap:6px;padding:8px 12px;background:transparent;transition:all .2s ease;color:#fff;cursor:grab}::ng-deep .layer-selector-body .item-list .group .item-list .item:last-child{border-bottom:none}::ng-deep .layer-selector-body .item-list .group .item-list .item:hover{background-color:#0000004d;transition:all .2s ease}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-preview{background:#fff;box-shadow:0 4px 12px #00000026;border-radius:4px}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-placeholder{background:#e3f2fd;opacity:.6}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon{flex-shrink:0}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.drag-indicator{color:#fff;font-size:16px;width:16px;height:16px;cursor:grab}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-on{color:#4caf50;font-size:18px;width:18px;height:18px;cursor:pointer;margin-left:auto;padding:3px;border-radius:3px}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-on:hover{background:#4caf5033}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.zoom-off{color:#f44336;font-size:16px;width:16px;height:16px}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-off{color:#f44336;font-size:18px;width:18px;height:18px;cursor:pointer;margin-left:auto;padding:3px;border-radius:3px}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-off:hover{background:#f4433633}::ng-deep .layer-selector-body .item-list .group .item-list .item>:nth-child(2){flex:1;font-size:13px;color:#fff;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}::ng-deep .layer-selector-body .item-list .group .item-list .item input[type=range]{width:80px;height:4px;margin:0 4px;flex-shrink:0}::ng-deep .mat-expansion-indicator svg{fill:#fff!important}.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:0 5px 15px #00000026;background:#fff;padding:10px 12px}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.item-list.cdk-drop-list-dragging .item:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}.power-on{color:#4caf50}.power-off{color:#f44336}.display-none{display:none}::ng-deep .layer-selector-body .item-list::-webkit-scrollbar{width:8px;border-radius:10px;border:5px solid transparent}::ng-deep .layer-selector-body .item-list::-webkit-scrollbar-thumb{background:#0000004d;border:5px solid transparent;border-radius:10px;background-clip:padding-box}::ng-deep .layer-selector-body .item-list::-webkit-scrollbar-thumb:hover{background:#0006;background-clip:padding-box;border:3px solid transparent}\n"], dependencies: [{ kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3$1.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: i3$1.RangeValueAccessor, selector: "input[type=range][formControlName],input[type=range][formControl],input[type=range][ngModel]" }, { kind: "directive", type: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i4.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i4.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i4.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i5.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i5.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i5.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i6.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }] });
|
|
275
533
|
}
|
|
276
534
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: LayerSelectorComponent, decorators: [{
|
|
277
535
|
type: Component,
|
|
278
536
|
args: [{ selector: 'lib-layer-selector', imports: [MatFormFieldModule, CommonModule, MatIconModule, FormsModule, DragDropModule,
|
|
279
|
-
MatExpansionModule, MatInputModule], template: "<div #layerSelectorIcon class=\"ol-unselectable ol-control layer-selector-icon\">\n <mat-icon (click)=\"toggleLayerSelector()\">layers</mat-icon>\n</div>\n<div #layerSelectorBody [class.display-none]=\"!showSelector\" class=\"ol-unselectable ol-control layer-selector-body\">\n <div class=\"search-section\">\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Filtrer</mat-label>\n <input \n matInput \n type=\"text\" \n [(ngModel)]=\"searchText\" \n (ngModelChange)=\"filteredLayerGroups=setfilteredGroups()\"\n placeholder=\"Skriv for at filtrere...\"\n />\n </mat-form-field>\n <mat-icon (click)=\"clearSearchText()\">undo</mat-icon>\n </div>\n \n <div\n cdkDropList\n [cdkDropListData]=\"filteredLayerGroups\"\n (cdkDropListDropped)=\"dropGroup($event)\"\n class=\"item-list\">\n @for (group of filteredLayerGroups; track group.id; let gIndex = $index) {\n <div class=\"group\" cdkDrag cdkDragPreviewDisabled>\n <mat-expansion-panel [(expanded)]=\"group.expanded\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n @if (group.expanded) {\n <mat-icon>arrow_upward</mat-icon>\n }\n @if (!group.expanded) {\n <mat-icon>arrow_downward</mat-icon> \n }\n {{ group.name }}\n </mat-panel-title>\n </mat-expansion-panel-header>\n <!-- This is only shown during drag -->\n <!-- <div *cdkDragPreview class=\"drag-preview\">\n {{ group.name }}\n </div> -->\n\n <!-- Placeholder to avoid jump -->\n <!-- <div *cdkDragPlaceholder class=\"drag-placeholder\"></div> -->\n\n <div\n cdkDropList\n [cdkDropListData]=\"group.layers\"\n (cdkDropListDropped)=\"dropLayer($event, group)\"\n class=\"item-list\">\n @for (layer of group.layers; track layer.id; let iIndex = $index) {\n <div class=\"item\" cdkDrag cdkDragPreviewDisabled>\n <mat-icon class=\"drag-indicator\">drag_indicator</mat-icon>\n <span>{{ layer.name }}</span>\n @if (layer.visible) {\n <mat-icon (click)=\"toggleLayer(layer)\" class=\"power-on\">power</mat-icon>(t\u00E6nd)\n }\n @if (!layer.visible) {\n <mat-icon (click)=\"toggleLayer(layer)\" class=\"power-off\">power</mat-icon>(sluk)\n }\n </div>\n }\n </div>\n </mat-expansion-panel>\n </div>\n }\n </div>\n</div>\n", styles: ["::ng-deep .layer-selector-icon{position:absolute;left:auto;right:4em;bottom:.5em;z-index:1000}::ng-deep .layer-selector-icon mat-icon{display:flex;justify-content:center;align-items:center;cursor:pointer;transition:all .3s ease;font-size:45px;width:50px;height:50px;background-color:#0004;border:1px solid rgba(255,255,255,.8);border-radius:8px;-webkit-text-fill-color:#334155;-webkit-text-stroke-width:.9px;-webkit-text-stroke-color:white}::ng-deep .layer-selector-icon mat-icon:hover{box-shadow:0 4px 12px #0003;color:#3e4b5e}::ng-deep .layer-selector-body{position:absolute;left:auto;right:4em;bottom:4em;z-index:1000;background:#fff;border-radius:8px;box-shadow:0 4px 20px #00000026;border:1px solid #e0e0e0;width:320px;max-height:500px;overflow:hidden;display:flex;flex-direction:column}::ng-deep .layer-selector-body .search-section{display:flex;align-items:center;gap:8px;padding:16px 16px 8px;background:#fff;border-bottom:1px solid #e0e0e0}::ng-deep .layer-selector-body .search-section mat-form-field{flex:1}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-outline{background:#fff}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-wrapper{padding-bottom:0}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-outline-thick{color:#1976d2}::ng-deep .layer-selector-body .search-section mat-form-field input{font-size:14px;padding:8px 0}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-label{color:#666;font-weight:500}::ng-deep .layer-selector-body .search-section mat-icon{color:#666;cursor:pointer;padding:8px;border-radius:4px;transition:all .2s ease;font-size:30px;width:20px;height:20px;display:flex;justify-content:center;align-items:end}::ng-deep .layer-selector-body .search-section mat-icon:hover{color:#334155}::ng-deep .layer-selector-body .item-list{flex:1;overflow-y:auto;padding:8px;max-height:400px}::ng-deep .layer-selector-body .item-list .group{margin-bottom:8px;border-radius:6px;overflow:hidden;box-shadow:0 -2px 2px #4868b20a,0 2px 2px #6a6f7517,0 1px 2px #4868b214}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel{box-shadow:none!important;border-radius:0!important}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header{padding:0 16px;height:48px;background:#f8f9fa;border-bottom:1px solid #e0e0e0}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header:hover{background:#e6e7ec}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title{align-items:center;gap:8px;font-weight:600;color:#333;font-size:14px}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title mat-icon{color:#666;font-size:18px;width:18px;height:18px;transition:transform .2s ease}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel .mat-expansion-panel-content{background:#fff}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel .mat-expansion-panel-content .mat-expansion-panel-body{padding:8px 0}::ng-deep .layer-selector-body .item-list .group .item-list{padding:0;max-height:none}::ng-deep .layer-selector-body .item-list .group .item-list .item{display:flex;align-items:center;gap:8px;padding:12px 16px;border-bottom:1px solid #f5f5f5;background:#fff;transition:all .2s ease;cursor:move}::ng-deep .layer-selector-body .item-list .group .item-list .item:last-child{border-bottom:none}::ng-deep .layer-selector-body .item-list .group .item-list .item:hover{background:#f8f9fa}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-preview{background:#fff;box-shadow:0 4px 12px #00000026;border-radius:4px}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-placeholder{background:#e3f2fd;opacity:.6}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.drag-indicator{color:#9e9e9e;font-size:18px;width:18px;height:18px;cursor:move}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-on{color:#4caf50;font-size:20px;width:20px;height:20px;cursor:pointer;margin-left:auto;padding:4px;border-radius:4px}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-on:hover{background:#e8f5e8}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-off{color:#f44336;font-size:20px;width:20px;height:20px;cursor:pointer;margin-left:auto;padding:4px;border-radius:4px}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-off:hover{background:#ffebee}::ng-deep .layer-selector-body .item-list .group .item-list .item>:nth-child(2){flex:1;font-size:14px;color:#333;margin:0}.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:0 5px 15px #00000026;background:#fff;padding:12px 16px}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.item-list.cdk-drop-list-dragging .item:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}.power-on{color:#4caf50}.power-off{color:#f44336}.display-none{display:none}::ng-deep .layer-selector-body .item-list{scrollbar-width:thin;scrollbar-color:#c1c1c1 transparent}\n"] }]
|
|
537
|
+
MatExpansionModule, MatInputModule], template: "<div #layerSelectorIcon class=\"ol-unselectable ol-control layer-selector-icon\">\n <mat-icon (click)=\"toggleLayerSelector()\">layers</mat-icon>\n</div>\n<div #layerSelectorBody [class.display-none]=\"!showSelector\" class=\"layer-selector-body-wrapper\" cdkDrag cdkDragBoundary=\".map-container\">\n <div class=\"drag-handle-selector\" cdkDragHandle>\n <mat-icon>drag_indicator</mat-icon>\n </div>\n <div class=\"ol-unselectable ol-control layer-selector-body\">\n <div class=\"search-section\">\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Filtrer</mat-label>\n <input \n matInput \n type=\"text\" \n [(ngModel)]=\"searchText\" \n (ngModelChange)=\"filteredLayerGroups=setfilteredGroups()\"\n placeholder=\"Skriv for at filtrere...\"\n />\n </mat-form-field>\n <mat-icon (click)=\"clearSearchText()\">undo</mat-icon>\n </div>\n \n <div\n cdkDropList\n [cdkDropListData]=\"filteredLayerGroups\"\n (cdkDropListDropped)=\"dropGroup($event)\"\n class=\"item-list\">\n @for (group of filteredLayerGroups; track group.id; let gIndex = $index) {\n <div class=\"group\" cdkDrag cdkDragPreviewDisabled>\n <mat-expansion-panel [(expanded)]=\"group.expanded\">\n <mat-expansion-panel-header>\n <mat-panel-title>\n @if (group.expanded) {\n <mat-icon>arrow_upward</mat-icon>\n }\n @if (!group.expanded) {\n <mat-icon>arrow_downward</mat-icon> \n }\n {{ group.name }} \n <mat-icon class=\"lightbulb\">lightbulb</mat-icon>\n ({{ group.noOfVisibleLayers }}/{{ group.layers.length }})\n @if (group.visible) {\n <mat-icon (click)=\"toggleGroup($event, group)\" class=\"power-on\">power</mat-icon>(t\u00E6nd)\n }\n @if (!group.visible) {\n <mat-icon (click)=\"toggleGroup($event, group)\" class=\"power-off\">power_off</mat-icon>(sluk)\n }\n </mat-panel-title>\n </mat-expansion-panel-header>\n <!-- This is only shown during drag -->\n <!-- <div *cdkDragPreview class=\"drag-preview\">\n {{ group.name }}\n </div> -->\n\n <!-- Placeholder to avoid jump -->\n <!-- <div *cdkDragPlaceholder class=\"drag-placeholder\"></div> -->\n\n <div\n cdkDropList\n [cdkDropListData]=\"group.layers\"\n (cdkDropListDropped)=\"dropLayer($event, group)\"\n class=\"item-list\">\n @for (layer of group.layers; track layer.id; let iIndex = $index) {\n <div class=\"item\" cdkDrag cdkDragPreviewDisabled>\n <mat-icon class=\"drag-indicator\">drag_indicator</mat-icon>\n <span>{{ layer.name }}</span>\n @if (layer.maxZoom < currentZoomLevel || layer.minZoom > currentZoomLevel) {\n <mat-icon class=\"zoom-off\">browser_not_supported</mat-icon>(zoom)\n }\n @if (layer.hasErrors) {\n <mat-icon class=\"zoom-off\">wifi_off</mat-icon>(fejle)\n }\n @if (layer.visible) {\n <mat-icon (click)=\"toggleLayer(layer)\" class=\"power-on\">power</mat-icon>(t\u00E6nd)\n }\n @if (!layer.visible) {\n <mat-icon (click)=\"toggleLayer(layer)\" class=\"power-off\">power_off</mat-icon>(sluk)\n }\n <input \n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.05\"\n [(ngModel)]=\"layer.opacity\"\n (input)=\"updateOpacity(layer)\"\n (mousedown)=\"stopDrag($event)\"\n (touchstart)=\"stopDrag($event)\"\n (pointerdown)=\"stopDrag($event)\"\n >\n </div>\n }\n </div>\n </mat-expansion-panel>\n </div>\n }\n </div>\n </div>\n</div>", styles: ["::ng-deep .layer-selector-icon{position:absolute;left:auto;right:4em;bottom:.5em;z-index:1000}::ng-deep .layer-selector-icon mat-icon{display:flex;justify-content:center;align-items:center;cursor:pointer;transition:all .3s ease;outline:none;border:0;height:40px;border-radius:5px;width:40px;background:#fff;box-shadow:#0000004d 0 1px 4px -1px;font-size:2em;color:#666}::ng-deep .layer-selector-icon mat-icon:hover{box-shadow:0 4px 12px #0003;color:#3e4b5e}.layer-selector-body-wrapper{position:absolute;left:auto;right:4em;bottom:4em;z-index:1000;cursor:grab}.layer-selector-body-wrapper.cdk-drag-dragging{opacity:.8;cursor:grab;z-index:1001}.layer-selector-body-wrapper .ol-control{border-radius:0}.drag-handle-selector{display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,#000 40%,transparent);border-radius:4px 4px 0 0;padding:4px;cursor:grab;box-shadow:0 -2px 8px #0003}.drag-handle-selector mat-icon{color:#fff;font-size:20px;width:20px;height:20px}::ng-deep .layer-selector-body{position:relative;left:auto;right:auto;bottom:auto;z-index:auto;background:color-mix(in srgb,#000 40%,transparent);box-shadow:0 4px 20px #00000026;width:480px;max-height:500px;overflow:hidden;display:flex;flex-direction:column}::ng-deep .layer-selector-body .search-section{display:flex;align-items:center;gap:6px;padding:8px 12px 6px}::ng-deep .layer-selector-body .search-section mat-form-field{flex:1}::ng-deep .layer-selector-body .search-section mat-form-field .mat-mdc-text-field-wrapper{padding-bottom:0}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-wrapper{padding-bottom:0;margin-bottom:0}::ng-deep .layer-selector-body .search-section mat-form-field .mat-mdc-form-field-infix{padding-bottom:6px;min-height:auto}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-outline{background:#fff}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-outline-thick{color:#1976d2}::ng-deep .layer-selector-body .search-section mat-form-field input{font-size:13px;padding:3px 0}::ng-deep .layer-selector-body .search-section mat-form-field .mat-form-field-label{color:#fff;font-weight:500;font-size:13px}::ng-deep .layer-selector-body .search-section mat-form-field .mat-mdc-form-field-subscript-wrapper{height:0;margin-top:0}::ng-deep .layer-selector-body .search-section mat-icon{color:#fff;cursor:pointer;padding:6px;border-radius:4px;transition:all .2s ease;font-size:24px;width:24px;height:24px;display:flex;justify-content:center;align-items:center}::ng-deep .layer-selector-body .search-section mat-icon:hover{color:#f9fafb}::ng-deep .layer-selector-body .item-list{flex:1;overflow-y:auto;padding:6px;max-height:400px}::ng-deep .layer-selector-body .item-list .group{margin-bottom:6px;overflow:hidden;box-shadow:0 -2px 2px #4868b20a,0 2px 2px #6a6f7517,0 1px 2px #4868b214}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel{box-shadow:none!important;border-radius:0!important}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header{padding:0 12px;height:40px;background:color-mix(in srgb,#000 55%,transparent)}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header:hover{background:color-mix(in srgb,#000 60%,transparent)!important}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title{align-items:center;gap:6px;font-weight:600;color:#fff;font-size:13px}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title mat-icon{color:#fff;font-size:16px;width:16px;height:16px;transition:transform .2s ease}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title mat-icon.lightbulb{color:#dfca0e}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title mat-icon.power-on{color:#4caf50}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel mat-expansion-panel-header mat-panel-title mat-icon.power-off{color:#f44336}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel .mat-expansion-panel-content{background:color-mix(in srgb,#000 40%,transparent)}::ng-deep .layer-selector-body .item-list .group mat-expansion-panel .mat-expansion-panel-content .mat-expansion-panel-body{padding:4px 0}::ng-deep .layer-selector-body .item-list .group .item-list{padding:0;max-height:none}::ng-deep .layer-selector-body .item-list .group .item-list .item{display:flex;align-items:center;gap:6px;padding:8px 12px;background:transparent;transition:all .2s ease;color:#fff;cursor:grab}::ng-deep .layer-selector-body .item-list .group .item-list .item:last-child{border-bottom:none}::ng-deep .layer-selector-body .item-list .group .item-list .item:hover{background-color:#0000004d;transition:all .2s ease}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-preview{background:#fff;box-shadow:0 4px 12px #00000026;border-radius:4px}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-placeholder{background:#e3f2fd;opacity:.6}::ng-deep .layer-selector-body .item-list .group .item-list .item.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon{flex-shrink:0}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.drag-indicator{color:#fff;font-size:16px;width:16px;height:16px;cursor:grab}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-on{color:#4caf50;font-size:18px;width:18px;height:18px;cursor:pointer;margin-left:auto;padding:3px;border-radius:3px}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-on:hover{background:#4caf5033}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.zoom-off{color:#f44336;font-size:16px;width:16px;height:16px}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-off{color:#f44336;font-size:18px;width:18px;height:18px;cursor:pointer;margin-left:auto;padding:3px;border-radius:3px}::ng-deep .layer-selector-body .item-list .group .item-list .item mat-icon.power-off:hover{background:#f4433633}::ng-deep .layer-selector-body .item-list .group .item-list .item>:nth-child(2){flex:1;font-size:13px;color:#fff;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}::ng-deep .layer-selector-body .item-list .group .item-list .item input[type=range]{width:80px;height:4px;margin:0 4px;flex-shrink:0}::ng-deep .mat-expansion-indicator svg{fill:#fff!important}.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:0 5px 15px #00000026;background:#fff;padding:10px 12px}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.item-list.cdk-drop-list-dragging .item:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}.power-on{color:#4caf50}.power-off{color:#f44336}.display-none{display:none}::ng-deep .layer-selector-body .item-list::-webkit-scrollbar{width:8px;border-radius:10px;border:5px solid transparent}::ng-deep .layer-selector-body .item-list::-webkit-scrollbar-thumb{background:#0000004d;border:5px solid transparent;border-radius:10px;background-clip:padding-box}::ng-deep .layer-selector-body .item-list::-webkit-scrollbar-thumb:hover{background:#0006;background-clip:padding-box;border:3px solid transparent}\n"] }]
|
|
280
538
|
}], propDecorators: { contentIcon: [{
|
|
281
539
|
type: ViewChild,
|
|
282
540
|
args: ['layerSelectorIcon', { static: false }]
|
|
@@ -287,40 +545,57 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImpo
|
|
|
287
545
|
type: Input
|
|
288
546
|
}], profile: [{
|
|
289
547
|
type: Input
|
|
548
|
+
}], currentZoomLevel: [{
|
|
549
|
+
type: Input
|
|
290
550
|
}] } });
|
|
291
551
|
|
|
552
|
+
// interface namedStyle {
|
|
553
|
+
// styleName: string;
|
|
554
|
+
// sldFeatureStyle: any;
|
|
555
|
+
// }
|
|
292
556
|
class KomponentSettingsHelperService {
|
|
293
557
|
_httpClient = inject(HttpClient);
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
-
});
|
|
558
|
+
styles = {};
|
|
559
|
+
// loadNamedStyleObjects(styleNames: string[], workspace: string, geoserver: GeoServer): Observable<namedStyle[]> {
|
|
560
|
+
// const headerstring = `${geoserver.userName}:${geoserver.password}`;
|
|
561
|
+
// const encoded = btoa(headerstring);
|
|
562
|
+
// const headers = new HttpHeaders().set('Authorization', `Basic ${encoded}`);
|
|
563
|
+
// const result$ = styleNames.map(styleName =>
|
|
564
|
+
// this._httpClient.get(`${geoserver.url}/rest/workspaces/${workspace}/styles/${styleName}.sld`, { headers, responseType: 'text' })
|
|
565
|
+
// .pipe(map(loadedStyle => {
|
|
566
|
+
// const sldObject = SLDReader.Reader(loadedStyle);
|
|
567
|
+
// const sldLayer = SLDReader.getLayer(sldObject);
|
|
568
|
+
// const sldStyles = SLDReader.getStyle(sldLayer);
|
|
569
|
+
// const sldFeatureStyle = sldStyles.featuretypestyles[0];
|
|
570
|
+
// return { styleName, sldFeatureStyle } as namedStyle
|
|
571
|
+
// })));
|
|
572
|
+
// return combineLatest(result$);
|
|
573
|
+
// }
|
|
574
|
+
_getSldStyleObject$(styleName, workspace, geoserver) {
|
|
575
|
+
return this.styles[styleName] ? of(this.styles[styleName]) :
|
|
576
|
+
this._loadStyleFromGeoServer$(styleName, workspace, geoserver)
|
|
577
|
+
.pipe(tap(loadedStyle => {
|
|
578
|
+
this.styles[styleName] = loadedStyle;
|
|
579
|
+
}));
|
|
317
580
|
}
|
|
318
|
-
|
|
581
|
+
_loadStyleFromGeoServer$(styleName, workspace, geoserver) {
|
|
319
582
|
const headerstring = `${geoserver.userName}:${geoserver.password}`;
|
|
320
583
|
const encoded = btoa(headerstring);
|
|
321
584
|
const headers = new HttpHeaders().set('Authorization', `Basic ${encoded}`);
|
|
322
585
|
const url = `${geoserver.url}/rest/workspaces/${workspace}/styles/${styleName}.sld`;
|
|
323
|
-
return this._httpClient.get(url, { headers, responseType: 'text' })
|
|
586
|
+
return this._httpClient.get(url, { headers, responseType: 'text' }).pipe(map(loadedStyle => {
|
|
587
|
+
const sldObject = SLDReader.Reader(loadedStyle);
|
|
588
|
+
const sldLayer = SLDReader.getLayer(sldObject);
|
|
589
|
+
const sldStyles = SLDReader.getStyle(sldLayer);
|
|
590
|
+
const sldFeatureStyle = sldStyles.featuretypestyles[0];
|
|
591
|
+
return sldFeatureStyle;
|
|
592
|
+
}));
|
|
593
|
+
}
|
|
594
|
+
getStyle(styleName, workspace, geoserver, styleType) {
|
|
595
|
+
return this._getSldStyleObject$(styleName, workspace, geoserver)
|
|
596
|
+
.pipe(map(loadedStyle => {
|
|
597
|
+
return SLDReader.createOlStyle(loadedStyle.rules[0], styleType);
|
|
598
|
+
}));
|
|
324
599
|
}
|
|
325
600
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: KomponentSettingsHelperService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
326
601
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: KomponentSettingsHelperService, providedIn: 'root' });
|
|
@@ -332,12 +607,720 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImpo
|
|
|
332
607
|
}]
|
|
333
608
|
}] });
|
|
334
609
|
|
|
610
|
+
class DrawLayerSourceService {
|
|
611
|
+
_features = new Subject;
|
|
612
|
+
features$ = this._features.asObservable();
|
|
613
|
+
source;
|
|
614
|
+
_disabled = false;
|
|
615
|
+
constructor() {
|
|
616
|
+
this.source = new VectorSource();
|
|
617
|
+
this._initListener();
|
|
618
|
+
}
|
|
619
|
+
_initListener() {
|
|
620
|
+
this.source.on(['addfeature', 'changefeature', 'removefeature'], evt => {
|
|
621
|
+
if (this._disabled) {
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
const features = this.source.getFeatures();
|
|
625
|
+
this._features.next(features);
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
get allFeatures() {
|
|
629
|
+
return this.source.getFeatures().map(f => {
|
|
630
|
+
// I want a static copy of the features.
|
|
631
|
+
const newFeature = f.clone();
|
|
632
|
+
// But cloning doesn't include the id and that is used for referencing a few places.
|
|
633
|
+
newFeature.setId(f.getId());
|
|
634
|
+
return newFeature;
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
remove(id) {
|
|
638
|
+
const feature = this.source.getFeatureById(id);
|
|
639
|
+
if (feature) {
|
|
640
|
+
this.source.removeFeature(feature);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
setFeatures(features) {
|
|
644
|
+
this._disabled = true;
|
|
645
|
+
this.source.clear();
|
|
646
|
+
this.source.addFeatures(features.map(f => {
|
|
647
|
+
const newFeature = f.clone();
|
|
648
|
+
newFeature.setId(f.getId());
|
|
649
|
+
return newFeature;
|
|
650
|
+
}));
|
|
651
|
+
this._features.next(features);
|
|
652
|
+
this._disabled = false;
|
|
653
|
+
}
|
|
654
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DrawLayerSourceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
655
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DrawLayerSourceService, providedIn: 'root' });
|
|
656
|
+
}
|
|
657
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DrawLayerSourceService, decorators: [{
|
|
658
|
+
type: Injectable,
|
|
659
|
+
args: [{
|
|
660
|
+
providedIn: 'root'
|
|
661
|
+
}]
|
|
662
|
+
}], ctorParameters: () => [] });
|
|
663
|
+
|
|
664
|
+
class FeatureHelperService {
|
|
665
|
+
FEATURE_TYPE_ID = 'TypeId';
|
|
666
|
+
FEATURE_LOCKED = 'LOCKED';
|
|
667
|
+
_idCounter = 0;
|
|
668
|
+
isLocked = (feature) => feature.get(this.FEATURE_LOCKED) === 'true';
|
|
669
|
+
lock(feature) { feature.set(this.FEATURE_LOCKED, 'true'); }
|
|
670
|
+
setTypeId(feature, typeId) {
|
|
671
|
+
feature.set(this.FEATURE_TYPE_ID, typeId);
|
|
672
|
+
}
|
|
673
|
+
typeId = (feature) => feature.get(this.FEATURE_TYPE_ID);
|
|
674
|
+
setId(feature) {
|
|
675
|
+
if (!feature.getId()) {
|
|
676
|
+
feature.setId(this._idCounter++);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: FeatureHelperService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
680
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: FeatureHelperService, providedIn: 'root' });
|
|
681
|
+
}
|
|
682
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: FeatureHelperService, decorators: [{
|
|
683
|
+
type: Injectable,
|
|
684
|
+
args: [{
|
|
685
|
+
providedIn: 'root'
|
|
686
|
+
}]
|
|
687
|
+
}] });
|
|
688
|
+
|
|
689
|
+
class UndoRedoService {
|
|
690
|
+
_drawlayerSourceService;
|
|
691
|
+
constructor(_drawlayerSourceService) {
|
|
692
|
+
this._drawlayerSourceService = _drawlayerSourceService;
|
|
693
|
+
}
|
|
694
|
+
disable() {
|
|
695
|
+
this._enabled = false;
|
|
696
|
+
}
|
|
697
|
+
enable() {
|
|
698
|
+
this._enabled = true;
|
|
699
|
+
}
|
|
700
|
+
_steps = [];
|
|
701
|
+
_currentStep = 0;
|
|
702
|
+
_changing = false;
|
|
703
|
+
_enabled = true;
|
|
704
|
+
init() {
|
|
705
|
+
this._steps[0] = this._drawlayerSourceService.allFeatures;
|
|
706
|
+
// For every change, add the current state
|
|
707
|
+
this._drawlayerSourceService.features$.pipe(filter(() => this._enabled)).subscribe({
|
|
708
|
+
next: () => {
|
|
709
|
+
if (this._changing) {
|
|
710
|
+
this._changing = false;
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
this._steps = [...this._steps.slice(0, this._currentStep + 1), this._drawlayerSourceService.allFeatures];
|
|
714
|
+
this._currentStep++;
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
get canUndo() {
|
|
719
|
+
return this._currentStep > 0;
|
|
720
|
+
}
|
|
721
|
+
get canRedo() {
|
|
722
|
+
return this._currentStep < this._steps.length - 1;
|
|
723
|
+
}
|
|
724
|
+
undo() {
|
|
725
|
+
if (this.canUndo) {
|
|
726
|
+
this._changing = true;
|
|
727
|
+
this._currentStep--;
|
|
728
|
+
const features = this._steps[this._currentStep];
|
|
729
|
+
this._drawlayerSourceService.setFeatures(features);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
redo() {
|
|
733
|
+
if (this.canRedo) {
|
|
734
|
+
this._changing = true;
|
|
735
|
+
this._currentStep++;
|
|
736
|
+
const features = this._steps[this._currentStep];
|
|
737
|
+
this._drawlayerSourceService.setFeatures(features);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
reset() {
|
|
741
|
+
this._changing = true;
|
|
742
|
+
if (this._currentStep === 0) {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
const features = this._steps[0];
|
|
746
|
+
this._drawlayerSourceService.setFeatures(features);
|
|
747
|
+
}
|
|
748
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: UndoRedoService, deps: [{ token: DrawLayerSourceService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
749
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: UndoRedoService, providedIn: 'root' });
|
|
750
|
+
}
|
|
751
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: UndoRedoService, decorators: [{
|
|
752
|
+
type: Injectable,
|
|
753
|
+
args: [{
|
|
754
|
+
providedIn: 'root'
|
|
755
|
+
}]
|
|
756
|
+
}], ctorParameters: () => [{ type: DrawLayerSourceService }] });
|
|
757
|
+
|
|
758
|
+
class GeometrySplitService {
|
|
759
|
+
geoJsonFormat = new GeoJSON();
|
|
760
|
+
_featureHelper = inject(FeatureHelperService);
|
|
761
|
+
/**
|
|
762
|
+
* Split polygons in a vector source using a buffered LineString.
|
|
763
|
+
* @param lineFeature The drawn LineString feature.
|
|
764
|
+
* @param vectorSource The source containing polygons to split.
|
|
765
|
+
* @param bufferMeters Width of the buffer in meters (default: 1).
|
|
766
|
+
*/
|
|
767
|
+
splitPolygonsWithBufferedLine(lineFeature, vectorSource, bufferMeters = 1) {
|
|
768
|
+
const lineGeoJSON = this.geoJsonFormat.writeFeatureObject(lineFeature);
|
|
769
|
+
// the buffer functions uses 4326 and makes a mess if you don't convert the projection
|
|
770
|
+
lineGeoJSON.geometry.coordinates = lineGeoJSON.geometry.coordinates.map(c => proj4('EPSG:25832', 'EPSG:4326', c));
|
|
771
|
+
// Buffer the line to create a narrow polygon
|
|
772
|
+
const bufferedLine = buffer(lineGeoJSON, bufferMeters, { units: 'meters' });
|
|
773
|
+
// Move back to 25832
|
|
774
|
+
bufferedLine.geometry.coordinates = bufferedLine.geometry.coordinates.map(a => a.map(c => proj4('EPSG:4326', 'EPSG:25832', c)));
|
|
775
|
+
const overlappingFeatures = vectorSource.getFeatures().filter(f => f.getGeometry()?.intersectsExtent(lineFeature.getGeometry()?.getExtent()));
|
|
776
|
+
overlappingFeatures.forEach((feature) => {
|
|
777
|
+
const overlappingFeatureGeoJson = this.geoJsonFormat.writeFeatureObject(feature);
|
|
778
|
+
const features = featureCollection([overlappingFeatureGeoJson, bufferedLine]);
|
|
779
|
+
const clipped = difference(features);
|
|
780
|
+
// If the result is not a MultiPolygon, the polygon was not clipped in two. In that case, I will do nothing.
|
|
781
|
+
if (clipped && clipped.geometry.type === 'MultiPolygon') {
|
|
782
|
+
vectorSource.removeFeature(feature);
|
|
783
|
+
const multipolygon = this.geoJsonFormat.readGeometry(clipped.geometry);
|
|
784
|
+
const polygons = multipolygon.getPolygons();
|
|
785
|
+
const newFeatures = polygons.map(p => {
|
|
786
|
+
const newFeature = feature.clone();
|
|
787
|
+
newFeature.setGeometry(p);
|
|
788
|
+
this._featureHelper.setId(newFeature);
|
|
789
|
+
return newFeature;
|
|
790
|
+
});
|
|
791
|
+
vectorSource.addFeatures(newFeatures);
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: GeometrySplitService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
796
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: GeometrySplitService, providedIn: 'root' });
|
|
797
|
+
}
|
|
798
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: GeometrySplitService, decorators: [{
|
|
799
|
+
type: Injectable,
|
|
800
|
+
args: [{
|
|
801
|
+
providedIn: 'root'
|
|
802
|
+
}]
|
|
803
|
+
}] });
|
|
804
|
+
|
|
805
|
+
class ToolboxComponent {
|
|
806
|
+
// Inputs
|
|
807
|
+
map;
|
|
808
|
+
showMeasureDistance = true;
|
|
809
|
+
showMeasureArea = true;
|
|
810
|
+
collapsed = false;
|
|
811
|
+
settings;
|
|
812
|
+
profile;
|
|
813
|
+
WKTInputEnabled = false;
|
|
814
|
+
deleteEnabled = false;
|
|
815
|
+
// Injects
|
|
816
|
+
_snackbar = inject(MatSnackBar);
|
|
817
|
+
_settingsHelper = inject(KomponentSettingsHelperService);
|
|
818
|
+
_drawLayerService = inject(DrawLayerSourceService);
|
|
819
|
+
_featureHelper = inject(FeatureHelperService);
|
|
820
|
+
_undoRedo = inject(UndoRedoService);
|
|
821
|
+
_geomSplitService = inject(GeometrySplitService);
|
|
822
|
+
selectedGeometrySetting = undefined;
|
|
823
|
+
showInputWKT = false;
|
|
824
|
+
WKTString;
|
|
825
|
+
activateShowInputWKT() {
|
|
826
|
+
this._clearAllInteractions();
|
|
827
|
+
this.showInputWKT = true;
|
|
828
|
+
}
|
|
829
|
+
cancelWKT() {
|
|
830
|
+
this._clearAllInteractions();
|
|
831
|
+
this.showInputWKT = false;
|
|
832
|
+
}
|
|
833
|
+
settingsChanged(evt) {
|
|
834
|
+
this._clearAllInteractions();
|
|
835
|
+
}
|
|
836
|
+
_activeFeaturesSource = new VectorSource();
|
|
837
|
+
_activeArea;
|
|
838
|
+
undo() {
|
|
839
|
+
this._undoRedo.undo();
|
|
840
|
+
}
|
|
841
|
+
redo() {
|
|
842
|
+
this._undoRedo.redo();
|
|
843
|
+
}
|
|
844
|
+
ReadWKT() {
|
|
845
|
+
this._clearAllInteractions();
|
|
846
|
+
this._setupActiveLayer();
|
|
847
|
+
if (!this.WKTString) {
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
const wktFormat = new WKT();
|
|
851
|
+
try {
|
|
852
|
+
const feature = wktFormat.readFeature(this.WKTString, { featureProjection: 'EPSG:25832', dataProjection: 'EPSG:25832' });
|
|
853
|
+
const featureType = feature.getGeometry()?.getType();
|
|
854
|
+
if (!this.selectedGeometrySetting?.availableGeometryTypes?.some(g => g === featureType)) {
|
|
855
|
+
this._snackbar.open(`Den WKT du indlæser er af typen ${featureType} og det er ikke tilladt for denne indstilling`, '', { duration: 4000 });
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
this._settingsHelper.getStyle(this.selectedGeometrySetting.style, this.profile.styleRepositoryWorkspace, this.profile.styleRepositoryGeoserver, featureType).subscribe({
|
|
859
|
+
next: featureStyle => {
|
|
860
|
+
feature.setStyle(featureStyle);
|
|
861
|
+
this._featureHelper.setTypeId(feature, this.selectedGeometrySetting?.typeId || '');
|
|
862
|
+
this._activeFeaturesSource.addFeature(feature);
|
|
863
|
+
const extent = buffer$1(feature.getGeometry().getExtent(), 10);
|
|
864
|
+
this.map.getView().fit(extent);
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
catch (ex) {
|
|
869
|
+
this._snackbar.open(`WKT kunne ikke indlæses - den er muligvis ikke formatteret korrekt`, '', { duration: 4000 });
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
_drawLayer;
|
|
873
|
+
_drawPolygon = new Draw({
|
|
874
|
+
type: 'Polygon',
|
|
875
|
+
source: this._drawLayerService.source
|
|
876
|
+
});
|
|
877
|
+
_actionSource = new VectorSource();
|
|
878
|
+
clipHole() {
|
|
879
|
+
this._clearAllInteractions();
|
|
880
|
+
const clipHoleDraw = new Draw({
|
|
881
|
+
source: this._actionSource,
|
|
882
|
+
type: 'Polygon'
|
|
883
|
+
});
|
|
884
|
+
this._setAsTemp(clipHoleDraw);
|
|
885
|
+
clipHoleDraw.on('drawend', evt => {
|
|
886
|
+
const overlappingFeatures = this._drawLayerService.source.getFeatures().filter(f => f.getGeometry()?.intersectsExtent(evt.feature.getGeometry().getExtent()));
|
|
887
|
+
const geoJsonFormat = new GeoJSON();
|
|
888
|
+
const holeGeoJson = geoJsonFormat.writeFeatureObject(evt.feature);
|
|
889
|
+
overlappingFeatures.forEach(f => {
|
|
890
|
+
const overlappingFeatureGeoJson = geoJsonFormat.writeFeatureObject(f);
|
|
891
|
+
// because the difference function takes featurecollection, I add the two features to a collection
|
|
892
|
+
// I have to cast this as any, because the featurecollection needs to be of Polygon or MultiPoly and it
|
|
893
|
+
// doesnt resolve to that.
|
|
894
|
+
const features = featureCollection([overlappingFeatureGeoJson, holeGeoJson]);
|
|
895
|
+
const clipped = difference(features);
|
|
896
|
+
if (clipped) {
|
|
897
|
+
const geom = geoJsonFormat.readGeometry(clipped.geometry);
|
|
898
|
+
f.setGeometry(geom);
|
|
899
|
+
}
|
|
900
|
+
});
|
|
901
|
+
});
|
|
902
|
+
this.map.addInteraction(clipHoleDraw);
|
|
903
|
+
}
|
|
904
|
+
split() {
|
|
905
|
+
this._clearAllInteractions();
|
|
906
|
+
const splitInteraction = new Draw({
|
|
907
|
+
type: 'LineString',
|
|
908
|
+
source: this._actionSource
|
|
909
|
+
});
|
|
910
|
+
this._setAsTemp(splitInteraction);
|
|
911
|
+
splitInteraction.on('drawend', evt => {
|
|
912
|
+
this._geomSplitService.splitPolygonsWithBufferedLine(evt.feature, this._drawLayerService.source, 0.01);
|
|
913
|
+
});
|
|
914
|
+
this.map.addInteraction(splitInteraction);
|
|
915
|
+
}
|
|
916
|
+
startDrawPoint() {
|
|
917
|
+
this._startDraw('Point');
|
|
918
|
+
}
|
|
919
|
+
startDrawLineString() {
|
|
920
|
+
this._startDraw('LineString');
|
|
921
|
+
}
|
|
922
|
+
startDrawPolygon() {
|
|
923
|
+
this._startDraw('Polygon');
|
|
924
|
+
}
|
|
925
|
+
_deleteSelect;
|
|
926
|
+
get deleteSelect() {
|
|
927
|
+
if (this._deleteSelect)
|
|
928
|
+
return this._deleteSelect;
|
|
929
|
+
this._deleteSelect = new Select({
|
|
930
|
+
layers: [this._drawLayer],
|
|
931
|
+
style: null, // ABSOLUTELY leave this as null! If not, select fires a change, because it applies a change of styling. LEAVE IT!
|
|
932
|
+
filter: feature => !this._featureHelper.isLocked(feature)
|
|
933
|
+
});
|
|
934
|
+
this._setAsTemp(this.deleteSelect);
|
|
935
|
+
this.deleteSelect.on('select', evt => {
|
|
936
|
+
this._drawLayerService.source.removeFeatures(evt.selected);
|
|
937
|
+
});
|
|
938
|
+
return this._deleteSelect;
|
|
939
|
+
}
|
|
940
|
+
_setAsTemp(interaction) {
|
|
941
|
+
interaction.set('temp', 'true');
|
|
942
|
+
}
|
|
943
|
+
_deleting = false;
|
|
944
|
+
startDelete() {
|
|
945
|
+
this._clearAllInteractions();
|
|
946
|
+
this._deleting = true;
|
|
947
|
+
this.map.addInteraction(this.deleteSelect);
|
|
948
|
+
}
|
|
949
|
+
startEditRemovePoints() {
|
|
950
|
+
this._clearAllInteractions();
|
|
951
|
+
const modifyRemove = new Modify({
|
|
952
|
+
source: this._drawLayerService.source,
|
|
953
|
+
// Make sure delete is allowed and other interactions are not.
|
|
954
|
+
deleteCondition: always,
|
|
955
|
+
insertVertexCondition: never
|
|
956
|
+
});
|
|
957
|
+
this._setAsTemp(modifyRemove);
|
|
958
|
+
this.map.addInteraction(modifyRemove);
|
|
959
|
+
}
|
|
960
|
+
startEdit() {
|
|
961
|
+
this._clearAllInteractions();
|
|
962
|
+
const modify = new Modify({
|
|
963
|
+
source: this._drawLayerService.source,
|
|
964
|
+
// No delete
|
|
965
|
+
deleteCondition: () => false,
|
|
966
|
+
// Allow moving existing and add new points
|
|
967
|
+
condition: always,
|
|
968
|
+
insertVertexCondition: always,
|
|
969
|
+
});
|
|
970
|
+
/* Modify fires changes to features a LOT, so to not flood the UndoRedo service, I disable until ending the modify
|
|
971
|
+
and then force an update, to add the updated feature to the undo-service. */
|
|
972
|
+
modify.on('modifystart', () => this._undoRedo.disable());
|
|
973
|
+
modify.on('modifyend', (evt) => { this._undoRedo.enable(); evt.features.forEach(f => f.set('updated', new Date().toISOString())); });
|
|
974
|
+
this._setAsTemp(modify);
|
|
975
|
+
this.map.addInteraction(modify);
|
|
976
|
+
}
|
|
977
|
+
_startDraw(geomType) {
|
|
978
|
+
this._clearAllInteractions();
|
|
979
|
+
if (!this.selectedGeometrySetting) {
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
this._settingsHelper.getStyle(this.selectedGeometrySetting.style, this.profile.styleRepositoryWorkspace, this.profile.styleRepositoryGeoserver, geomType)
|
|
983
|
+
.subscribe({
|
|
984
|
+
next: style => {
|
|
985
|
+
const drawLineString = new Draw({
|
|
986
|
+
type: geomType,
|
|
987
|
+
source: this._drawLayerService.source,
|
|
988
|
+
style: style
|
|
989
|
+
});
|
|
990
|
+
drawLineString.on('drawend', evt => {
|
|
991
|
+
this._featureHelper.setTypeId(evt.feature, this.selectedGeometrySetting?.typeId || '');
|
|
992
|
+
this._featureHelper.setId(evt.feature);
|
|
993
|
+
evt.feature.setStyle(style);
|
|
994
|
+
});
|
|
995
|
+
this._setAsTemp(drawLineString);
|
|
996
|
+
this._setDrawLayer();
|
|
997
|
+
this.map.addInteraction(drawLineString);
|
|
998
|
+
this.map.getTargetElement().style.cursor = 'crosshair'; // Placeholder for now
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
_clearDrawInteractions() {
|
|
1003
|
+
this.map.getTargetElement().style.cursor = '';
|
|
1004
|
+
this.map.getInteractions().forEach(i => {
|
|
1005
|
+
if (i.get('temp') === 'true') {
|
|
1006
|
+
this.map.removeInteraction(i);
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
compareGeometrySetting = (g1, g2) => g1 && g2 && g1.typeId === g2.typeId;
|
|
1011
|
+
_areaSource = new VectorSource();
|
|
1012
|
+
_areaLayer = new VectorLayer({
|
|
1013
|
+
source: this._areaSource,
|
|
1014
|
+
style: (feature) => {
|
|
1015
|
+
const geom = feature.getGeometry();
|
|
1016
|
+
const area = getArea(geom);
|
|
1017
|
+
const formattedArea = area > 1000000 ? `${(area / 1000000).toFixed(2)} km2` : `${area.toFixed(2)} m2`;
|
|
1018
|
+
return new Style({
|
|
1019
|
+
stroke: new Stroke({
|
|
1020
|
+
color: 'blue',
|
|
1021
|
+
width: 2
|
|
1022
|
+
}),
|
|
1023
|
+
fill: new Fill({
|
|
1024
|
+
color: 'grey'
|
|
1025
|
+
}),
|
|
1026
|
+
text: new Text({
|
|
1027
|
+
text: formattedArea,
|
|
1028
|
+
font: '12px Calibri,sans-serif',
|
|
1029
|
+
fill: new Fill({ color: '#000' }),
|
|
1030
|
+
stroke: new Stroke({ color: '#fff', width: 3 }),
|
|
1031
|
+
placement: 'line'
|
|
1032
|
+
})
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
});
|
|
1036
|
+
_distanceLabelSource = new VectorSource();
|
|
1037
|
+
_distanceLabelLayer = new VectorLayer({
|
|
1038
|
+
source: this._distanceLabelSource,
|
|
1039
|
+
style: (feature) => {
|
|
1040
|
+
return new Style({
|
|
1041
|
+
text: new Text({
|
|
1042
|
+
text: feature.get('label'),
|
|
1043
|
+
font: '12px Calibri,sans-serif',
|
|
1044
|
+
fill: new Fill({ color: '#000' }),
|
|
1045
|
+
stroke: new Stroke({ color: '#fff', width: 3 })
|
|
1046
|
+
})
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
});
|
|
1050
|
+
_distanceSource = new VectorSource();
|
|
1051
|
+
_distanceLayer = new VectorLayer({
|
|
1052
|
+
source: this._distanceSource,
|
|
1053
|
+
style: (feature) => {
|
|
1054
|
+
const geom = feature.getGeometry();
|
|
1055
|
+
const length = getLength(geom);
|
|
1056
|
+
const formattedLength = `${length.toFixed(2)} m`;
|
|
1057
|
+
return new Style({
|
|
1058
|
+
stroke: new Stroke({
|
|
1059
|
+
color: 'blue',
|
|
1060
|
+
width: 2
|
|
1061
|
+
}),
|
|
1062
|
+
text: new Text({
|
|
1063
|
+
text: formattedLength,
|
|
1064
|
+
font: '12px Calibri,sans-serif',
|
|
1065
|
+
fill: new Fill({ color: '#000' }),
|
|
1066
|
+
stroke: new Stroke({ color: '#fff', width: 3 }),
|
|
1067
|
+
placement: 'line'
|
|
1068
|
+
})
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
1072
|
+
_distanceMeasureDraw = new Draw({
|
|
1073
|
+
type: 'LineString',
|
|
1074
|
+
source: this._distanceLayer.getSource(),
|
|
1075
|
+
});
|
|
1076
|
+
_areaMeasureDraw = new Draw({
|
|
1077
|
+
type: 'Polygon',
|
|
1078
|
+
source: this._areaSource
|
|
1079
|
+
});
|
|
1080
|
+
_active = 'No';
|
|
1081
|
+
_setupActiveLayer() {
|
|
1082
|
+
if (!this._activeArea) {
|
|
1083
|
+
this._activeArea = new VectorLayer({
|
|
1084
|
+
source: this._activeFeaturesSource
|
|
1085
|
+
});
|
|
1086
|
+
this.map.addLayer(this._activeArea);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
constructor() {
|
|
1090
|
+
// TODO: Move out from constructor
|
|
1091
|
+
// In order to add the distance to each segment of the distance measure feature, we need some extra handling like this.
|
|
1092
|
+
this._distanceMeasureDraw.on('drawstart', (evt) => {
|
|
1093
|
+
const sketch = evt.feature;
|
|
1094
|
+
sketch.getGeometry().on('change', (geomEvt) => {
|
|
1095
|
+
const geom = geomEvt.target;
|
|
1096
|
+
const coords = geom.getCoordinates();
|
|
1097
|
+
this._distanceLabelSource.clear(); // Clear existing labels for each change
|
|
1098
|
+
let totalLength = 0;
|
|
1099
|
+
for (let i = 0; i < coords.length - 1; i++) {
|
|
1100
|
+
const c1 = coords[i];
|
|
1101
|
+
const c2 = coords[i + 1];
|
|
1102
|
+
const segment = new LineString([c1, c2]);
|
|
1103
|
+
const length = getLength(segment);
|
|
1104
|
+
totalLength += length;
|
|
1105
|
+
const mid = [(c1[0] + c2[0]) / 2, (c1[1] + c2[1]) / 2];
|
|
1106
|
+
const formatted = `${length.toFixed(2)} m`;
|
|
1107
|
+
const labelFeature = new Feature({
|
|
1108
|
+
geometry: new Point(mid),
|
|
1109
|
+
label: formatted
|
|
1110
|
+
});
|
|
1111
|
+
this._distanceLabelSource.addFeature(labelFeature);
|
|
1112
|
+
}
|
|
1113
|
+
// Add total length at the last point
|
|
1114
|
+
if (coords.length > 1) {
|
|
1115
|
+
const endPoint = coords[coords.length - 1];
|
|
1116
|
+
const formattedTotal = `${totalLength.toFixed(2)} m`;
|
|
1117
|
+
const totalFeature = new Feature({
|
|
1118
|
+
geometry: new Point(endPoint),
|
|
1119
|
+
label: `Total: ${formattedTotal}`
|
|
1120
|
+
});
|
|
1121
|
+
this._distanceLabelSource.addFeature(totalFeature);
|
|
1122
|
+
}
|
|
1123
|
+
});
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
_clearAllInteractions() {
|
|
1127
|
+
this.stopMeasureArea();
|
|
1128
|
+
this.stopMeasureLength();
|
|
1129
|
+
this._deleting = false;
|
|
1130
|
+
this._clearDrawInteractions();
|
|
1131
|
+
this._undoRedo.enable();
|
|
1132
|
+
}
|
|
1133
|
+
_setDrawLayer() {
|
|
1134
|
+
if (!this._drawLayer) {
|
|
1135
|
+
this._drawLayer = new VectorLayer({
|
|
1136
|
+
source: this._drawLayerService.source,
|
|
1137
|
+
// source: this._drawLayerSource,
|
|
1138
|
+
});
|
|
1139
|
+
this._drawLayer.set('DRAWLAYER', 'true');
|
|
1140
|
+
this.map.addLayer(this._drawLayer);
|
|
1141
|
+
this._setupMouseStyleCursor();
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
ngOnInit() {
|
|
1145
|
+
if (this.settings.geometryTypeSettings && this.settings.geometryTypeSettings.length === 1) {
|
|
1146
|
+
this.selectedGeometrySetting = this.settings.geometryTypeSettings[0];
|
|
1147
|
+
}
|
|
1148
|
+
this._drawPolygon.on('drawend', evt => {
|
|
1149
|
+
this._featureHelper.setTypeId(evt.feature, this.selectedGeometrySetting?.typeId || '');
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
_setupMouseStyleCursor() {
|
|
1153
|
+
this.map.on('pointermove', (evt) => {
|
|
1154
|
+
const pixel = this.map.getEventPixel(evt.originalEvent);
|
|
1155
|
+
const feature = this.map.forEachFeatureAtPixel(pixel, (f) => f, {
|
|
1156
|
+
layerFilter: (layer) => {
|
|
1157
|
+
return layer.get('DRAWLAYER') === 'true';
|
|
1158
|
+
}
|
|
1159
|
+
});
|
|
1160
|
+
const cursor = feature && this._deleting && !feature.get('LOCKED') ? 'crosshair' : ''; // TODO: Set actual style for delete
|
|
1161
|
+
this.map.getTargetElement().style.cursor = cursor;
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
toggleCollapsed() {
|
|
1165
|
+
this.collapsed = !this.collapsed;
|
|
1166
|
+
if (this.collapsed) {
|
|
1167
|
+
this.stopMeasureArea();
|
|
1168
|
+
this.stopMeasureLength();
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
startMeasureArea() {
|
|
1172
|
+
if (this._active === 'Area') {
|
|
1173
|
+
this.stopMeasureArea();
|
|
1174
|
+
this._active = 'No';
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
// this.geometryTypeSettings
|
|
1178
|
+
this._clearDrawInteractions();
|
|
1179
|
+
this.stopMeasureLength();
|
|
1180
|
+
this.map.addLayer(this._areaLayer);
|
|
1181
|
+
this.map.addInteraction(this._areaMeasureDraw);
|
|
1182
|
+
this._active = 'Area';
|
|
1183
|
+
}
|
|
1184
|
+
stopMeasureArea() {
|
|
1185
|
+
this.map.getTargetElement().style.cursor = '';
|
|
1186
|
+
this._areaSource.clear();
|
|
1187
|
+
this.map.removeInteraction(this._areaMeasureDraw);
|
|
1188
|
+
this.map.removeLayer(this._areaLayer);
|
|
1189
|
+
}
|
|
1190
|
+
stopMeasureLength() {
|
|
1191
|
+
this.map.getTargetElement().style.cursor = '';
|
|
1192
|
+
this._distanceSource.clear();
|
|
1193
|
+
this._distanceLabelSource.clear();
|
|
1194
|
+
this.map.removeInteraction(this._distanceMeasureDraw);
|
|
1195
|
+
this.map.removeLayer(this._distanceLayer);
|
|
1196
|
+
this.map.removeLayer(this._distanceLabelLayer);
|
|
1197
|
+
}
|
|
1198
|
+
startMeasureLength() {
|
|
1199
|
+
if (this._active === 'Distance') {
|
|
1200
|
+
this.stopMeasureLength();
|
|
1201
|
+
this._active = 'No';
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
this._clearAllInteractions();
|
|
1205
|
+
this.map.addLayer(this._distanceLayer);
|
|
1206
|
+
this.map.addLayer(this._distanceLabelLayer);
|
|
1207
|
+
this.map.addInteraction(this._distanceMeasureDraw);
|
|
1208
|
+
this._active = 'Distance';
|
|
1209
|
+
}
|
|
1210
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ToolboxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1211
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: ToolboxComponent, isStandalone: true, selector: "map-toolbox", inputs: { map: "map", showMeasureDistance: "showMeasureDistance", showMeasureArea: "showMeasureArea", collapsed: "collapsed", settings: "settings", profile: "profile", WKTInputEnabled: "WKTInputEnabled", deleteEnabled: "deleteEnabled" }, ngImport: i0, template: "<div class=\"toolbox-wrapper\" cdkDrag cdkDragBoundary=\".map-container\">\n <div class=\"drag-handle-toolbox\" cdkDragHandle>\n <mat-icon>drag_indicator</mat-icon>\n </div>\n <div class=\"toolbox-container\">\n <mat-icon class=\"toggle-icon\" (click)=\"toggleCollapsed()\">{{ collapsed ? 'expand_more' : 'expand_less' }}</mat-icon>\n @if(!collapsed) {\n <div class=\"toolbox-content\">\n @if (settings.geometryTypeSettings && settings.geometryTypeSettings.length) {\n <mat-select class=\"geometry-selector\" (selectionChange)=\"settingsChanged($event)\" [(ngModel)]=\"selectedGeometrySetting\" [compareWith]=\"compareGeometrySetting\">\n @for (setting of settings.geometryTypeSettings; track setting) {\n <mat-option [value]=\"setting\">{{setting.typeName}}</mat-option>\n }\n </mat-select> \n }\n <mat-icon class=\"compact-icon\" (click)=\"undo()\" matTooltip=\"Fortryd\" matTooltipPosition=\"right\">undo</mat-icon>\n <mat-icon class=\"compact-icon\" (click)=\"redo()\" matTooltip=\"Gentag\" matTooltipPosition=\"right\">redo</mat-icon>\n @if(settings.editEnabled) {\n <mat-icon class=\"compact-icon\" (click)=\"startEdit()\" matTooltip=\"Rediger\" matTooltipPosition=\"right\">edit_square</mat-icon>\n <mat-icon class=\"compact-icon\" (click)=\"startEditRemovePoints()\" matTooltip=\"Fjern punkter\" matTooltipPosition=\"right\">close</mat-icon>\n }\n @if(settings.cutHoleEnabled) {\n <mat-icon class=\"compact-icon\" (click)=\"clipHole()\" matTooltip=\"Klip hul\" matTooltipPosition=\"right\">restaurant</mat-icon>\n }\n @if(settings.splitEnabled) {\n <mat-icon class=\"compact-icon\" (click)=\"split()\" matTooltip=\"Sk\u00E6r over\" matTooltipPosition=\"right\">carpenter</mat-icon>\n }\n @if(selectedGeometrySetting && selectedGeometrySetting.availableGeometryTypes?.length) {\n <div class=\"geometry-tools\">\n @for(geomType of selectedGeometrySetting.availableGeometryTypes;track geomType) {\n @if(geomType === 'Polygon') {\n <button class=\"compact-button primary\" (click)=\"startDrawPolygon()\">Polygon</button>\n }\n @if(geomType === 'LineString') {\n <button class=\"compact-button primary\" (click)=\"startDrawLineString()\">LineString</button>\n }\n @if(geomType === 'Point') {\n <button class=\"compact-button primary\" (click)=\"startDrawPoint()\">Punkter</button>\n }\n }\n <button class=\"compact-button secondary\" (click)=\"activateShowInputWKT()\">WKT</button>\n </div>\n @if(showInputWKT) {\n <div class=\"wkt-section\">\n <input class=\"compact-input\" [(ngModel)]=\"WKTString\">\n <div class=\"wkt-actions\">\n <button class=\"compact-button primary\" (click)=\"ReadWKT()\">Indl\u00E6s WKT</button>\n <button class=\"compact-button\" (click)=\"cancelWKT()\">Annuller</button>\n </div>\n </div>\n }\n }\n <div class=\"measurement-tools\">\n @if (deleteEnabled) {\n <mat-icon class=\"compact-icon\" (click)=\"startDelete()\">delete</mat-icon>\n }\n @if (showMeasureDistance) {\n <mat-icon class=\"compact-icon\" (click)=\"startMeasureLength()\">straighten</mat-icon>\n }\n @if(showMeasureArea) {\n <mat-icon class=\"compact-icon\" (click)=\"startMeasureArea()\">square_foot</mat-icon>\n } \n </div>\n </div>\n }\n </div>\n</div>", styles: [".toolbox-wrapper{position:absolute;left:1em;top:17em;z-index:1000;cursor:grab}.toolbox-wrapper.cdk-drag-dragging{opacity:.8;cursor:grab;z-index:1001}.drag-handle-toolbox{display:flex;align-items:center;justify-content:center;background:#fff;border-radius:4px 4px 0 0;padding:4px;cursor:grab}.drag-handle-toolbox mat-icon{color:#546e7a;font-size:18px;width:18px;height:18px}:host{position:relative;display:flex;justify-content:center}:host.expanded{width:auto;min-width:220px;padding:12px}.toolbox-container{display:flex;flex-direction:column;align-items:center;width:100%;gap:10px;background:#fff;box-shadow:#0000004d 0 1px 4px -1px;padding:10px;width:auto;min-width:24px;transition:all .3s ease;cursor:default}.toggle-icon{cursor:pointer;color:#546e7a;transition:all .2s ease;-webkit-user-select:none;user-select:none;font-size:22px;width:22px;height:22px;display:flex;align-items:center;justify-content:center;border-radius:6px}.toggle-icon:hover{color:#1976d2;background:#1976d214}.toolbox-content{display:flex;flex-direction:column;width:100%;gap:10px;animation:slideDown .3s cubic-bezier(.4,0,.2,1)}.geometry-selector{width:100%}.geometry-selector ::ng-deep .mat-mdc-select{font-size:14px;line-height:1.4;border-radius:6px}.geometry-selector ::ng-deep .mat-mdc-select-trigger{height:32px;min-height:32px;padding:0 10px;border:1px solid #e0e0e0;border-radius:6px;background:#fff;transition:all .2s ease}.geometry-selector ::ng-deep .mat-mdc-select-trigger:hover{border-color:#1976d2;background:#fafbfc}.geometry-selector ::ng-deep .mat-mdc-select-value{font-size:14px;font-weight:500;color:#333}.geometry-selector ::ng-deep .mat-mdc-select-arrow-wrapper{height:16px;transform:scale(.85)}.geometry-selector ::ng-deep .mat-mdc-form-field-infix{min-height:32px;padding:6px 0}.geometry-selector ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}::ng-deep .mat-mdc-select-panel{min-width:fit-content!important;max-width:320px!important;background:#fff!important;border:1px solid #e0e0e0!important;border-radius:8px!important;box-shadow:0 8px 24px #0000001f,0 2px 6px #00000014!important;margin-top:6px!important;padding:4px 0!important}::ng-deep .mat-mdc-select-panel .mat-mdc-option{font-size:14px!important;min-height:36px!important;padding:8px 14px!important;transition:all .15s ease!important}::ng-deep .mat-mdc-select-panel .mat-mdc-option:hover:not(.mat-mdc-option-disabled){background:#f5f7fa!important}::ng-deep .mat-mdc-select-panel .mat-mdc-option.mat-active{background:#e3f2fd!important;color:#1976d2!important}::ng-deep .mat-mdc-select-panel .mat-mdc-option .mdc-list-item__primary-text{font-size:14px!important;font-weight:500!important;color:#333!important}::ng-deep .cdk-overlay-pane{z-index:1001}::ng-deep .cdk-overlay-backdrop{z-index:1000}.geometry-tools{display:grid;grid-template-columns:1fr 1fr;gap:6px;width:100%}.measurement-tools{display:flex;gap:8px;justify-content:center;padding-top:10px;border-top:1px solid rgba(0,0,0,.06)}.wkt-section{display:flex;flex-direction:column;gap:8px;padding-top:10px;border-top:1px solid rgba(0,0,0,.06);animation:fadeIn .2s ease}.wkt-actions{display:flex;gap:6px;justify-content:space-between}.compact-button{padding:6px 10px;border:none;border-radius:6px;background:#f1f3f5;color:#495057;font-size:12px;font-weight:600;cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1);text-transform:uppercase;letter-spacing:.4px;min-height:32px;display:flex;align-items:center;justify-content:center;flex:1;box-shadow:0 1px 2px #0000000d}.compact-button:hover{background:#e9ecef;box-shadow:0 2px 4px #0000001a}.compact-button.primary{background:linear-gradient(135deg,#1976d2,#1565c0);color:#fff;box-shadow:0 2px 4px #1976d233}.compact-button.primary:hover{background:linear-gradient(135deg,#1565c0,#0d47a1);box-shadow:0 3px 8px #1976d24d}.compact-button.secondary{background:#6c757d;color:#fff}.compact-button.secondary:hover{background:#5a6268}.compact-icon{cursor:pointer;color:#546e7a;transition:all .2s cubic-bezier(.4,0,.2,1);-webkit-user-select:none;user-select:none;width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:6px;background:#fff;border:1px solid #e0e0e0;box-shadow:0 1px 2px #0000000d}.compact-icon:hover{color:#1976d2;background:#e3f2fd;border-color:#1976d2;box-shadow:0 2px 4px #0000001a}.compact-icon:active{box-shadow:0 1px 2px #00000014}.compact-input{padding:8px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;transition:all .2s ease;background:#fff;color:#333}.compact-input:focus{outline:none;border-color:#1976d2;background:#fafbfc;box-shadow:0 0 0 3px #1976d21a}@keyframes slideDown{0%{opacity:0}to{opacity:1}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@media (max-width: 768px){.toolbox-wrapper{left:.5em;top:8em}:host{padding:8px;min-width:32px}:host.expanded{min-width:200px;padding:10px}.compact-button{font-size:11px;padding:5px 8px;min-height:28px}.compact-icon{width:28px;height:28px;font-size:18px}::ng-deep .mat-mdc-select-panel{max-width:280px!important}::ng-deep .mat-mdc-select-panel .mat-mdc-option{font-size:13px!important;min-height:32px!important;padding:6px 12px!important}}@media (max-width: 480px){.toolbox-wrapper{transform-origin:left top}.geometry-tools{gap:4px}.compact-button{font-size:10px;letter-spacing:.2px}}::ng-deep .mat-mdc-simple-snack-bar{background-color:#fff!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3$1.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: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatOptionModule }, { kind: "component", type: i3$2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i4.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i4.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i6$1.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] });
|
|
1212
|
+
}
|
|
1213
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ToolboxComponent, decorators: [{
|
|
1214
|
+
type: Component,
|
|
1215
|
+
args: [{ selector: 'map-toolbox', imports: [FormsModule, CommonModule, MatIconModule, MatOptionModule, MatSelectModule, DragDropModule, MatTooltipModule], template: "<div class=\"toolbox-wrapper\" cdkDrag cdkDragBoundary=\".map-container\">\n <div class=\"drag-handle-toolbox\" cdkDragHandle>\n <mat-icon>drag_indicator</mat-icon>\n </div>\n <div class=\"toolbox-container\">\n <mat-icon class=\"toggle-icon\" (click)=\"toggleCollapsed()\">{{ collapsed ? 'expand_more' : 'expand_less' }}</mat-icon>\n @if(!collapsed) {\n <div class=\"toolbox-content\">\n @if (settings.geometryTypeSettings && settings.geometryTypeSettings.length) {\n <mat-select class=\"geometry-selector\" (selectionChange)=\"settingsChanged($event)\" [(ngModel)]=\"selectedGeometrySetting\" [compareWith]=\"compareGeometrySetting\">\n @for (setting of settings.geometryTypeSettings; track setting) {\n <mat-option [value]=\"setting\">{{setting.typeName}}</mat-option>\n }\n </mat-select> \n }\n <mat-icon class=\"compact-icon\" (click)=\"undo()\" matTooltip=\"Fortryd\" matTooltipPosition=\"right\">undo</mat-icon>\n <mat-icon class=\"compact-icon\" (click)=\"redo()\" matTooltip=\"Gentag\" matTooltipPosition=\"right\">redo</mat-icon>\n @if(settings.editEnabled) {\n <mat-icon class=\"compact-icon\" (click)=\"startEdit()\" matTooltip=\"Rediger\" matTooltipPosition=\"right\">edit_square</mat-icon>\n <mat-icon class=\"compact-icon\" (click)=\"startEditRemovePoints()\" matTooltip=\"Fjern punkter\" matTooltipPosition=\"right\">close</mat-icon>\n }\n @if(settings.cutHoleEnabled) {\n <mat-icon class=\"compact-icon\" (click)=\"clipHole()\" matTooltip=\"Klip hul\" matTooltipPosition=\"right\">restaurant</mat-icon>\n }\n @if(settings.splitEnabled) {\n <mat-icon class=\"compact-icon\" (click)=\"split()\" matTooltip=\"Sk\u00E6r over\" matTooltipPosition=\"right\">carpenter</mat-icon>\n }\n @if(selectedGeometrySetting && selectedGeometrySetting.availableGeometryTypes?.length) {\n <div class=\"geometry-tools\">\n @for(geomType of selectedGeometrySetting.availableGeometryTypes;track geomType) {\n @if(geomType === 'Polygon') {\n <button class=\"compact-button primary\" (click)=\"startDrawPolygon()\">Polygon</button>\n }\n @if(geomType === 'LineString') {\n <button class=\"compact-button primary\" (click)=\"startDrawLineString()\">LineString</button>\n }\n @if(geomType === 'Point') {\n <button class=\"compact-button primary\" (click)=\"startDrawPoint()\">Punkter</button>\n }\n }\n <button class=\"compact-button secondary\" (click)=\"activateShowInputWKT()\">WKT</button>\n </div>\n @if(showInputWKT) {\n <div class=\"wkt-section\">\n <input class=\"compact-input\" [(ngModel)]=\"WKTString\">\n <div class=\"wkt-actions\">\n <button class=\"compact-button primary\" (click)=\"ReadWKT()\">Indl\u00E6s WKT</button>\n <button class=\"compact-button\" (click)=\"cancelWKT()\">Annuller</button>\n </div>\n </div>\n }\n }\n <div class=\"measurement-tools\">\n @if (deleteEnabled) {\n <mat-icon class=\"compact-icon\" (click)=\"startDelete()\">delete</mat-icon>\n }\n @if (showMeasureDistance) {\n <mat-icon class=\"compact-icon\" (click)=\"startMeasureLength()\">straighten</mat-icon>\n }\n @if(showMeasureArea) {\n <mat-icon class=\"compact-icon\" (click)=\"startMeasureArea()\">square_foot</mat-icon>\n } \n </div>\n </div>\n }\n </div>\n</div>", styles: [".toolbox-wrapper{position:absolute;left:1em;top:17em;z-index:1000;cursor:grab}.toolbox-wrapper.cdk-drag-dragging{opacity:.8;cursor:grab;z-index:1001}.drag-handle-toolbox{display:flex;align-items:center;justify-content:center;background:#fff;border-radius:4px 4px 0 0;padding:4px;cursor:grab}.drag-handle-toolbox mat-icon{color:#546e7a;font-size:18px;width:18px;height:18px}:host{position:relative;display:flex;justify-content:center}:host.expanded{width:auto;min-width:220px;padding:12px}.toolbox-container{display:flex;flex-direction:column;align-items:center;width:100%;gap:10px;background:#fff;box-shadow:#0000004d 0 1px 4px -1px;padding:10px;width:auto;min-width:24px;transition:all .3s ease;cursor:default}.toggle-icon{cursor:pointer;color:#546e7a;transition:all .2s ease;-webkit-user-select:none;user-select:none;font-size:22px;width:22px;height:22px;display:flex;align-items:center;justify-content:center;border-radius:6px}.toggle-icon:hover{color:#1976d2;background:#1976d214}.toolbox-content{display:flex;flex-direction:column;width:100%;gap:10px;animation:slideDown .3s cubic-bezier(.4,0,.2,1)}.geometry-selector{width:100%}.geometry-selector ::ng-deep .mat-mdc-select{font-size:14px;line-height:1.4;border-radius:6px}.geometry-selector ::ng-deep .mat-mdc-select-trigger{height:32px;min-height:32px;padding:0 10px;border:1px solid #e0e0e0;border-radius:6px;background:#fff;transition:all .2s ease}.geometry-selector ::ng-deep .mat-mdc-select-trigger:hover{border-color:#1976d2;background:#fafbfc}.geometry-selector ::ng-deep .mat-mdc-select-value{font-size:14px;font-weight:500;color:#333}.geometry-selector ::ng-deep .mat-mdc-select-arrow-wrapper{height:16px;transform:scale(.85)}.geometry-selector ::ng-deep .mat-mdc-form-field-infix{min-height:32px;padding:6px 0}.geometry-selector ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}::ng-deep .mat-mdc-select-panel{min-width:fit-content!important;max-width:320px!important;background:#fff!important;border:1px solid #e0e0e0!important;border-radius:8px!important;box-shadow:0 8px 24px #0000001f,0 2px 6px #00000014!important;margin-top:6px!important;padding:4px 0!important}::ng-deep .mat-mdc-select-panel .mat-mdc-option{font-size:14px!important;min-height:36px!important;padding:8px 14px!important;transition:all .15s ease!important}::ng-deep .mat-mdc-select-panel .mat-mdc-option:hover:not(.mat-mdc-option-disabled){background:#f5f7fa!important}::ng-deep .mat-mdc-select-panel .mat-mdc-option.mat-active{background:#e3f2fd!important;color:#1976d2!important}::ng-deep .mat-mdc-select-panel .mat-mdc-option .mdc-list-item__primary-text{font-size:14px!important;font-weight:500!important;color:#333!important}::ng-deep .cdk-overlay-pane{z-index:1001}::ng-deep .cdk-overlay-backdrop{z-index:1000}.geometry-tools{display:grid;grid-template-columns:1fr 1fr;gap:6px;width:100%}.measurement-tools{display:flex;gap:8px;justify-content:center;padding-top:10px;border-top:1px solid rgba(0,0,0,.06)}.wkt-section{display:flex;flex-direction:column;gap:8px;padding-top:10px;border-top:1px solid rgba(0,0,0,.06);animation:fadeIn .2s ease}.wkt-actions{display:flex;gap:6px;justify-content:space-between}.compact-button{padding:6px 10px;border:none;border-radius:6px;background:#f1f3f5;color:#495057;font-size:12px;font-weight:600;cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1);text-transform:uppercase;letter-spacing:.4px;min-height:32px;display:flex;align-items:center;justify-content:center;flex:1;box-shadow:0 1px 2px #0000000d}.compact-button:hover{background:#e9ecef;box-shadow:0 2px 4px #0000001a}.compact-button.primary{background:linear-gradient(135deg,#1976d2,#1565c0);color:#fff;box-shadow:0 2px 4px #1976d233}.compact-button.primary:hover{background:linear-gradient(135deg,#1565c0,#0d47a1);box-shadow:0 3px 8px #1976d24d}.compact-button.secondary{background:#6c757d;color:#fff}.compact-button.secondary:hover{background:#5a6268}.compact-icon{cursor:pointer;color:#546e7a;transition:all .2s cubic-bezier(.4,0,.2,1);-webkit-user-select:none;user-select:none;width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:6px;background:#fff;border:1px solid #e0e0e0;box-shadow:0 1px 2px #0000000d}.compact-icon:hover{color:#1976d2;background:#e3f2fd;border-color:#1976d2;box-shadow:0 2px 4px #0000001a}.compact-icon:active{box-shadow:0 1px 2px #00000014}.compact-input{padding:8px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;transition:all .2s ease;background:#fff;color:#333}.compact-input:focus{outline:none;border-color:#1976d2;background:#fafbfc;box-shadow:0 0 0 3px #1976d21a}@keyframes slideDown{0%{opacity:0}to{opacity:1}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@media (max-width: 768px){.toolbox-wrapper{left:.5em;top:8em}:host{padding:8px;min-width:32px}:host.expanded{min-width:200px;padding:10px}.compact-button{font-size:11px;padding:5px 8px;min-height:28px}.compact-icon{width:28px;height:28px;font-size:18px}::ng-deep .mat-mdc-select-panel{max-width:280px!important}::ng-deep .mat-mdc-select-panel .mat-mdc-option{font-size:13px!important;min-height:32px!important;padding:6px 12px!important}}@media (max-width: 480px){.toolbox-wrapper{transform-origin:left top}.geometry-tools{gap:4px}.compact-button{font-size:10px;letter-spacing:.2px}}::ng-deep .mat-mdc-simple-snack-bar{background-color:#fff!important}\n"] }]
|
|
1216
|
+
}], ctorParameters: () => [], propDecorators: { map: [{
|
|
1217
|
+
type: Input,
|
|
1218
|
+
args: [{ required: true }]
|
|
1219
|
+
}], showMeasureDistance: [{
|
|
1220
|
+
type: Input
|
|
1221
|
+
}], showMeasureArea: [{
|
|
1222
|
+
type: Input
|
|
1223
|
+
}], collapsed: [{
|
|
1224
|
+
type: Input
|
|
1225
|
+
}], settings: [{
|
|
1226
|
+
type: Input,
|
|
1227
|
+
args: [{ required: true }]
|
|
1228
|
+
}], profile: [{
|
|
1229
|
+
type: Input,
|
|
1230
|
+
args: [{ required: true }]
|
|
1231
|
+
}], WKTInputEnabled: [{
|
|
1232
|
+
type: Input
|
|
1233
|
+
}], deleteEnabled: [{
|
|
1234
|
+
type: Input
|
|
1235
|
+
}] } });
|
|
1236
|
+
|
|
1237
|
+
class ActiveObjectsComponent {
|
|
1238
|
+
_drawLayerService = inject(DrawLayerSourceService);
|
|
1239
|
+
features$ = this._drawLayerService.features$;
|
|
1240
|
+
settings;
|
|
1241
|
+
activeFeatures = [];
|
|
1242
|
+
_featureHelper = inject(FeatureHelperService);
|
|
1243
|
+
constructor() {
|
|
1244
|
+
this.features$.subscribe({
|
|
1245
|
+
next: features => {
|
|
1246
|
+
const featureTypes = [...new Set(features.map(f => this._featureHelper.typeId(f)).filter(typeId => !!typeId))];
|
|
1247
|
+
const featuresObj = featureTypes
|
|
1248
|
+
.map(ft => ({ featureType: ft,
|
|
1249
|
+
display: this.settings.geometryTypeSettings.find(gts => gts.typeId === ft).typeName,
|
|
1250
|
+
features: features.filter(f => this._featureHelper.typeId(f) === ft)
|
|
1251
|
+
.map(f => ({ id: f.getId(), locked: this._featureHelper.isLocked(f), area: this._getAreaString(f) })) }));
|
|
1252
|
+
this.activeFeatures = [...featuresObj];
|
|
1253
|
+
}
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
_getAreaString(feature) {
|
|
1257
|
+
if (feature.getGeometry()?.getType() !== 'Polygon') { // We only have polygon, linestring and points and only polygons have area
|
|
1258
|
+
return '';
|
|
1259
|
+
}
|
|
1260
|
+
return this.settings.sizeInHa ? `(${(getArea(feature.getGeometry()) / 10000).toFixed(2)} ha)` : `(${getArea(feature.getGeometry()).toFixed(2)} m2)`;
|
|
1261
|
+
}
|
|
1262
|
+
highlight(id) {
|
|
1263
|
+
console.log("🚀 ~ ActiveObjectsComponent ~ highlight ~ id:", id);
|
|
1264
|
+
}
|
|
1265
|
+
unhighlight(id) {
|
|
1266
|
+
console.log("🚀 ~ ActiveObjectsComponent ~ unhighlight ~ id:", id);
|
|
1267
|
+
}
|
|
1268
|
+
delete(id) {
|
|
1269
|
+
this._drawLayerService.remove(id);
|
|
1270
|
+
this.unhighlight(id);
|
|
1271
|
+
}
|
|
1272
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ActiveObjectsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1273
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: ActiveObjectsComponent, isStandalone: true, selector: "activeObjects", inputs: { settings: "settings" }, ngImport: i0, template: "<div class=\"active-objects-wrapper\" cdkDrag cdkDragBoundary=\".map-container\">\n <div class=\"drag-handle-active-objects\" cdkDragHandle>\n <mat-icon>drag_indicator</mat-icon>\n </div>\n <div class=\"active-objects-content\">\n <h4>Aktive flader</h4>\n @for(featureTypeObj of activeFeatures; track featureTypeObj.featureType) {\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <span class=\"panel-title\">\n {{featureTypeObj.display}} ({{featureTypeObj.features.length}})\n </span>\n </mat-expansion-panel-header>\n <div class=\"item-list\">\n @for(item of featureTypeObj.features; track item.id) {\n <div class=\"item\" (mouseenter)=\"highlight(item.id)\" (mouseleave)=\"unhighlight(item.id)\">\n <div class=\"item-text\">\n <span class=\"item-id\">{{item.id}}</span>\n <span class=\"item-area\">{{item.area}}</span>\n </div>\n <mat-icon *ngIf=\"!item.locked\" (click)=\"delete(item.id)\">delete</mat-icon>\n </div>\n }\n </div>\n </mat-expansion-panel>\n }\n </div> \n</div>", styles: ["@charset \"UTF-8\";.active-objects-wrapper{position:absolute;top:2em;left:5em;z-index:1000;cursor:grab;border-radius:8px;box-shadow:0 2px 10px #0000001a;background:color-mix(in srgb,#000 60%,transparent);max-width:295px;width:295px}.active-objects-wrapper.cdk-drag-dragging{opacity:.8;cursor:grab;z-index:1001}mat-expansion-panel ::ng-deep .mat-expansion-panel-header .panel-title{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:220px}.item{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-radius:4px;background:color-mix(in srgb,#000 60%,transparent)!important;transition:all .2s ease;font-size:14px;color:#fff;min-height:40px;cursor:default}.item .item-text{flex:1;min-width:0;display:flex;flex-direction:column;overflow:hidden}.item .item-id{font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-bottom:2px}.item .item-area{font-size:12px;color:#ccc;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.item mat-icon{cursor:pointer;color:#f44336;font-size:18px;width:18px;height:18px;flex-shrink:0;margin-left:8px}.item mat-icon:hover{color:#d32f2f}.drag-handle-active-objects{display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,#000 0%,transparent);border-radius:8px 8px 0 0;padding:4px;cursor:grab}.drag-handle-active-objects mat-icon{color:#fff;font-size:18px;width:18px;height:18px}.active-objects-content{display:block;max-height:500px;overflow:auto}.active-objects-content::-webkit-scrollbar{width:8px;border-radius:10px;border:5px solid transparent}.active-objects-content::-webkit-scrollbar-thumb{background:#0000004d;border:5px solid transparent;border-radius:10px;background-clip:padding-box}.active-objects-content::-webkit-scrollbar-thumb:hover{background:#0006;background-clip:padding-box;border:3px solid transparent}h4{margin:0;padding:16px;background:color-mix(in srgb,#000 40%,transparent);font-weight:600;color:#fff;text-align:center;cursor:default}mat-expansion-panel{border-radius:0!important;box-shadow:none!important}mat-expansion-panel:last-child{border-bottom:none}mat-expansion-panel ::ng-deep .mat-expansion-panel-header{padding:0 16px;font-weight:500}mat-expansion-panel ::ng-deep .mat-expansion-panel-header .mat-content{display:flex;justify-content:space-between;align-items:center;color:#fff}mat-expansion-panel ::ng-deep .mat-expansion-panel-body{padding:0}.item-list{display:flex;flex-direction:column;gap:4px;padding:8px;max-height:300px;overflow-y:auto}.item-list::-webkit-scrollbar{width:6px}.item-list::-webkit-scrollbar-track{background:#f1f1f1;border-radius:3px}.item-list::-webkit-scrollbar-thumb{background:#c1c1c1;border-radius:3px}.item-list::-webkit-scrollbar-thumb:hover{background:#a8a8a8}@media (max-width: 768px){.active-objects-wrapper{right:.5em;top:8em;max-width:280px;width:280px}.item{font-size:13px;padding:6px 10px}h4{padding:12px;font-size:14px}}@media (max-width: 480px){.active-objects-wrapper{right:.5em;left:.5em;max-width:calc(100vw - 1em);width:auto}.item-list{max-height:200px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i5.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i5.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i4.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i4.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }] });
|
|
1274
|
+
}
|
|
1275
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ActiveObjectsComponent, decorators: [{
|
|
1276
|
+
type: Component,
|
|
1277
|
+
args: [{ selector: 'activeObjects', imports: [CommonModule, MatExpansionModule, MatIconModule, DragDropModule], template: "<div class=\"active-objects-wrapper\" cdkDrag cdkDragBoundary=\".map-container\">\n <div class=\"drag-handle-active-objects\" cdkDragHandle>\n <mat-icon>drag_indicator</mat-icon>\n </div>\n <div class=\"active-objects-content\">\n <h4>Aktive flader</h4>\n @for(featureTypeObj of activeFeatures; track featureTypeObj.featureType) {\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <span class=\"panel-title\">\n {{featureTypeObj.display}} ({{featureTypeObj.features.length}})\n </span>\n </mat-expansion-panel-header>\n <div class=\"item-list\">\n @for(item of featureTypeObj.features; track item.id) {\n <div class=\"item\" (mouseenter)=\"highlight(item.id)\" (mouseleave)=\"unhighlight(item.id)\">\n <div class=\"item-text\">\n <span class=\"item-id\">{{item.id}}</span>\n <span class=\"item-area\">{{item.area}}</span>\n </div>\n <mat-icon *ngIf=\"!item.locked\" (click)=\"delete(item.id)\">delete</mat-icon>\n </div>\n }\n </div>\n </mat-expansion-panel>\n }\n </div> \n</div>", styles: ["@charset \"UTF-8\";.active-objects-wrapper{position:absolute;top:2em;left:5em;z-index:1000;cursor:grab;border-radius:8px;box-shadow:0 2px 10px #0000001a;background:color-mix(in srgb,#000 60%,transparent);max-width:295px;width:295px}.active-objects-wrapper.cdk-drag-dragging{opacity:.8;cursor:grab;z-index:1001}mat-expansion-panel ::ng-deep .mat-expansion-panel-header .panel-title{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:220px}.item{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-radius:4px;background:color-mix(in srgb,#000 60%,transparent)!important;transition:all .2s ease;font-size:14px;color:#fff;min-height:40px;cursor:default}.item .item-text{flex:1;min-width:0;display:flex;flex-direction:column;overflow:hidden}.item .item-id{font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-bottom:2px}.item .item-area{font-size:12px;color:#ccc;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.item mat-icon{cursor:pointer;color:#f44336;font-size:18px;width:18px;height:18px;flex-shrink:0;margin-left:8px}.item mat-icon:hover{color:#d32f2f}.drag-handle-active-objects{display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,#000 0%,transparent);border-radius:8px 8px 0 0;padding:4px;cursor:grab}.drag-handle-active-objects mat-icon{color:#fff;font-size:18px;width:18px;height:18px}.active-objects-content{display:block;max-height:500px;overflow:auto}.active-objects-content::-webkit-scrollbar{width:8px;border-radius:10px;border:5px solid transparent}.active-objects-content::-webkit-scrollbar-thumb{background:#0000004d;border:5px solid transparent;border-radius:10px;background-clip:padding-box}.active-objects-content::-webkit-scrollbar-thumb:hover{background:#0006;background-clip:padding-box;border:3px solid transparent}h4{margin:0;padding:16px;background:color-mix(in srgb,#000 40%,transparent);font-weight:600;color:#fff;text-align:center;cursor:default}mat-expansion-panel{border-radius:0!important;box-shadow:none!important}mat-expansion-panel:last-child{border-bottom:none}mat-expansion-panel ::ng-deep .mat-expansion-panel-header{padding:0 16px;font-weight:500}mat-expansion-panel ::ng-deep .mat-expansion-panel-header .mat-content{display:flex;justify-content:space-between;align-items:center;color:#fff}mat-expansion-panel ::ng-deep .mat-expansion-panel-body{padding:0}.item-list{display:flex;flex-direction:column;gap:4px;padding:8px;max-height:300px;overflow-y:auto}.item-list::-webkit-scrollbar{width:6px}.item-list::-webkit-scrollbar-track{background:#f1f1f1;border-radius:3px}.item-list::-webkit-scrollbar-thumb{background:#c1c1c1;border-radius:3px}.item-list::-webkit-scrollbar-thumb:hover{background:#a8a8a8}@media (max-width: 768px){.active-objects-wrapper{right:.5em;top:8em;max-width:280px;width:280px}.item{font-size:13px;padding:6px 10px}h4{padding:12px;font-size:14px}}@media (max-width: 480px){.active-objects-wrapper{right:.5em;left:.5em;max-width:calc(100vw - 1em);width:auto}.item-list{max-height:200px}}\n"] }]
|
|
1278
|
+
}], ctorParameters: () => [], propDecorators: { settings: [{
|
|
1279
|
+
type: Input,
|
|
1280
|
+
args: [{ required: true }]
|
|
1281
|
+
}] } });
|
|
1282
|
+
|
|
1283
|
+
class MapSearchComponent {
|
|
1284
|
+
searchAddress = '';
|
|
1285
|
+
allResults = ['address1', 'address2', 'address3', 'address4'];
|
|
1286
|
+
filteredResults = [];
|
|
1287
|
+
ngOnInit() {
|
|
1288
|
+
this.filterResults();
|
|
1289
|
+
}
|
|
1290
|
+
filterResults() {
|
|
1291
|
+
if (!this.searchAddress) {
|
|
1292
|
+
this.filteredResults = [];
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
this.filteredResults = this.allResults.filter(r => r.toLowerCase().includes(this.searchAddress.toLowerCase()));
|
|
1296
|
+
}
|
|
1297
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: MapSearchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1298
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.15", type: MapSearchComponent, isStandalone: true, selector: "lib-map-search", ngImport: i0, template: "<div class=\"search-container\" cdkDrag cdkDragBoundary=\".map-container\">\n <div class=\"drag-handle\" cdkDragHandle>\n <mat-icon>drag_indicator</mat-icon>\n </div>\n <mat-form-field appearance=\"outline\" class=\"search-field\">\n <mat-icon class=\"search-icon\" matPrefix>search</mat-icon>\n <mat-label>S\u00F8ge</mat-label>\n <input\n type=\"text\"\n matInput\n [(ngModel)]=\"searchAddress\"\n [matAutocomplete]=\"auto\"\n (input)=\"filterResults()\"\n />\n <mat-autocomplete #auto=\"matAutocomplete\">\n <mat-option *ngFor=\"let result of filteredResults\" [value]=\"result\">\n {{ result }}\n </mat-option>\n </mat-autocomplete>\n </mat-form-field>\n</div>", styles: [".search-container{position:absolute;top:2em;left:25em;z-index:1000;cursor:grab}.search-container.cdk-drag-dragging{opacity:.8;cursor:grab}.drag-handle{display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,#000 60%,transparent);padding:4px;cursor:grab;border-radius:8px 8px 0 0}.drag-handle mat-icon{color:#fff;font-size:20px;width:20px;height:20px}::ng-deep .mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-text-field__input{color:#fff!important}::ng-deep .search-icon{color:#fff!important;font-size:20px;width:20px;height:20px;transition:color .3s ease}::ng-deep .search-icon:hover .search-icon{color:#fff!important}::ng-deep .search-icon.mat-focused ::ng-deep .search-icon-button .search-icon{color:#fff!important}.search-field{width:290px;cursor:pointer}.search-field ::ng-deep .mat-mdc-form-field-focus-overlay{background-color:transparent}.search-field ::ng-deep .mdc-notched-outline .mdc-notched-outline__leading,.search-field ::ng-deep .mdc-notched-outline .mdc-notched-outline__notch,.search-field ::ng-deep .mdc-notched-outline .mdc-notched-outline__trailing{border-color:transparent!important;border-width:1.5px!important;transition:border-color .3s ease,border-width .3s ease}.search-field:not(.mat-focused):hover ::ng-deep .mdc-notched-outline .mdc-notched-outline__leading,.search-field:not(.mat-focused):hover ::ng-deep .mdc-notched-outline .mdc-notched-outline__notch,.search-field:not(.mat-focused):hover ::ng-deep .mdc-notched-outline .mdc-notched-outline__trailing{border-color:transparent!important}.search-field ::ng-deep .mat-mdc-form-field-label{color:#666!important;font-weight:500;font-size:15px}.search-field ::ng-deep .mat-mdc-form-field.mat-focused .mat-mdc-form-field-label{color:#2196f3!important}.search-field ::ng-deep .mat-mdc-input-element{font-size:15px;color:#333;font-weight:400;line-height:1.5}.search-field ::ng-deep .mat-mdc-input-element::placeholder{color:#999;font-weight:400;opacity:.8}.search-field ::ng-deep .mat-icon.search-icon{color:#fff;margin-left:12px;margin-right:-8px;transition:color .3s ease}.search-field.mat-focused ::ng-deep .mat-icon.search-icon{color:#2196f3}.search-field ::ng-deep .mdc-floating-label{padding:0 4px;left:40px!important}::ng-deep .mat-mdc-form-field.mat-form-field-animations-enabled .mdc-text-field__input{margin-left:12px}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .mdc-list-item__primary-text{color:#fff!important}::ng-deep .mat-mdc-option .mat-pseudo-checkbox-minimal{color:#fff!important}::ng-deep .mat-mdc-autocomplete-panel{border-radius:12px!important;max-height:320px!important;background:color-mix(in srgb,#000 60%,transparent)!important;box-shadow:0 8px 24px #00000026!important;margin-top:8px!important;overflow:hidden!important}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option{min-height:52px!important;margin:4px 8px!important;border-radius:8px!important;transition:all .2s cubic-bezier(.4,0,.2,1);position:relative;overflow:hidden}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option:hover:not(.mdc-list-item--disabled){background-color:#0000004d!important}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .mdc-list-item__primary-text{font-size:14px;font-weight:400;color:inherit;line-height:1.5}::ng-deep .mat-mdc-autocomplete-panel::-webkit-scrollbar{width:6px}::ng-deep .mat-mdc-autocomplete-panel::-webkit-scrollbar-track{background:#f1f1f1;border-radius:0 12px 12px 0}::ng-deep .mat-mdc-autocomplete-panel::-webkit-scrollbar-thumb{background:#c1c1c1;border-radius:3px;transition:background .3s ease}::ng-deep .mat-mdc-autocomplete-panel::-webkit-scrollbar-thumb:hover{background:#a8a8a8}::ng-deep .mat-mdc-text-field-wrapper{background:color-mix(in srgb,#000 60%,transparent);border-radius:0 0 12px 12px;box-shadow:#0000004d 0 1px 4px -1px;transition:all .3s cubic-bezier(.4,0,.2,1)}::ng-deep .mat-mdc-text-field-wrapper:hover{box-shadow:0 4px 16px #00000026}::ng-deep .mat-mdc-form-field:not(.mat-form-field-disabled) .mat-mdc-floating-label.mdc-floating-label{color:#fff!important}@media (max-width: 768px){.search-container{left:2em;top:1.5em}.search-field{width:320px}}@media (max-width: 480px){.search-container{left:1.5em;right:1.5em}.search-field{width:calc(100vw - 3em)}.search-field ::ng-deep .mat-mdc-input-element{font-size:14px}::ng-deep .mat-mdc-autocomplete-panel{max-height:240px!important}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option{min-height:48px!important;padding:10px 16px!important}}::ng-deep .mdc-text-field{border-top-left-radius:0!important;border-top-right-radius:0!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3$1.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: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i6.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatAutocompleteModule }, { kind: "component", type: i5$1.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "component", type: i3$2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i5$1.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "ngmodule", type: MatOptionModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i4.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i4.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }] });
|
|
1299
|
+
}
|
|
1300
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: MapSearchComponent, decorators: [{
|
|
1301
|
+
type: Component,
|
|
1302
|
+
args: [{ selector: 'lib-map-search', imports: [
|
|
1303
|
+
CommonModule,
|
|
1304
|
+
FormsModule,
|
|
1305
|
+
MatFormFieldModule,
|
|
1306
|
+
MatInputModule,
|
|
1307
|
+
MatAutocompleteModule,
|
|
1308
|
+
MatOptionModule,
|
|
1309
|
+
MatIconModule,
|
|
1310
|
+
DragDropModule
|
|
1311
|
+
], template: "<div class=\"search-container\" cdkDrag cdkDragBoundary=\".map-container\">\n <div class=\"drag-handle\" cdkDragHandle>\n <mat-icon>drag_indicator</mat-icon>\n </div>\n <mat-form-field appearance=\"outline\" class=\"search-field\">\n <mat-icon class=\"search-icon\" matPrefix>search</mat-icon>\n <mat-label>S\u00F8ge</mat-label>\n <input\n type=\"text\"\n matInput\n [(ngModel)]=\"searchAddress\"\n [matAutocomplete]=\"auto\"\n (input)=\"filterResults()\"\n />\n <mat-autocomplete #auto=\"matAutocomplete\">\n <mat-option *ngFor=\"let result of filteredResults\" [value]=\"result\">\n {{ result }}\n </mat-option>\n </mat-autocomplete>\n </mat-form-field>\n</div>", styles: [".search-container{position:absolute;top:2em;left:25em;z-index:1000;cursor:grab}.search-container.cdk-drag-dragging{opacity:.8;cursor:grab}.drag-handle{display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,#000 60%,transparent);padding:4px;cursor:grab;border-radius:8px 8px 0 0}.drag-handle mat-icon{color:#fff;font-size:20px;width:20px;height:20px}::ng-deep .mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-text-field__input{color:#fff!important}::ng-deep .search-icon{color:#fff!important;font-size:20px;width:20px;height:20px;transition:color .3s ease}::ng-deep .search-icon:hover .search-icon{color:#fff!important}::ng-deep .search-icon.mat-focused ::ng-deep .search-icon-button .search-icon{color:#fff!important}.search-field{width:290px;cursor:pointer}.search-field ::ng-deep .mat-mdc-form-field-focus-overlay{background-color:transparent}.search-field ::ng-deep .mdc-notched-outline .mdc-notched-outline__leading,.search-field ::ng-deep .mdc-notched-outline .mdc-notched-outline__notch,.search-field ::ng-deep .mdc-notched-outline .mdc-notched-outline__trailing{border-color:transparent!important;border-width:1.5px!important;transition:border-color .3s ease,border-width .3s ease}.search-field:not(.mat-focused):hover ::ng-deep .mdc-notched-outline .mdc-notched-outline__leading,.search-field:not(.mat-focused):hover ::ng-deep .mdc-notched-outline .mdc-notched-outline__notch,.search-field:not(.mat-focused):hover ::ng-deep .mdc-notched-outline .mdc-notched-outline__trailing{border-color:transparent!important}.search-field ::ng-deep .mat-mdc-form-field-label{color:#666!important;font-weight:500;font-size:15px}.search-field ::ng-deep .mat-mdc-form-field.mat-focused .mat-mdc-form-field-label{color:#2196f3!important}.search-field ::ng-deep .mat-mdc-input-element{font-size:15px;color:#333;font-weight:400;line-height:1.5}.search-field ::ng-deep .mat-mdc-input-element::placeholder{color:#999;font-weight:400;opacity:.8}.search-field ::ng-deep .mat-icon.search-icon{color:#fff;margin-left:12px;margin-right:-8px;transition:color .3s ease}.search-field.mat-focused ::ng-deep .mat-icon.search-icon{color:#2196f3}.search-field ::ng-deep .mdc-floating-label{padding:0 4px;left:40px!important}::ng-deep .mat-mdc-form-field.mat-form-field-animations-enabled .mdc-text-field__input{margin-left:12px}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .mdc-list-item__primary-text{color:#fff!important}::ng-deep .mat-mdc-option .mat-pseudo-checkbox-minimal{color:#fff!important}::ng-deep .mat-mdc-autocomplete-panel{border-radius:12px!important;max-height:320px!important;background:color-mix(in srgb,#000 60%,transparent)!important;box-shadow:0 8px 24px #00000026!important;margin-top:8px!important;overflow:hidden!important}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option{min-height:52px!important;margin:4px 8px!important;border-radius:8px!important;transition:all .2s cubic-bezier(.4,0,.2,1);position:relative;overflow:hidden}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option:hover:not(.mdc-list-item--disabled){background-color:#0000004d!important}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .mdc-list-item__primary-text{font-size:14px;font-weight:400;color:inherit;line-height:1.5}::ng-deep .mat-mdc-autocomplete-panel::-webkit-scrollbar{width:6px}::ng-deep .mat-mdc-autocomplete-panel::-webkit-scrollbar-track{background:#f1f1f1;border-radius:0 12px 12px 0}::ng-deep .mat-mdc-autocomplete-panel::-webkit-scrollbar-thumb{background:#c1c1c1;border-radius:3px;transition:background .3s ease}::ng-deep .mat-mdc-autocomplete-panel::-webkit-scrollbar-thumb:hover{background:#a8a8a8}::ng-deep .mat-mdc-text-field-wrapper{background:color-mix(in srgb,#000 60%,transparent);border-radius:0 0 12px 12px;box-shadow:#0000004d 0 1px 4px -1px;transition:all .3s cubic-bezier(.4,0,.2,1)}::ng-deep .mat-mdc-text-field-wrapper:hover{box-shadow:0 4px 16px #00000026}::ng-deep .mat-mdc-form-field:not(.mat-form-field-disabled) .mat-mdc-floating-label.mdc-floating-label{color:#fff!important}@media (max-width: 768px){.search-container{left:2em;top:1.5em}.search-field{width:320px}}@media (max-width: 480px){.search-container{left:1.5em;right:1.5em}.search-field{width:calc(100vw - 3em)}.search-field ::ng-deep .mat-mdc-input-element{font-size:14px}::ng-deep .mat-mdc-autocomplete-panel{max-height:240px!important}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option{min-height:48px!important;padding:10px 16px!important}}::ng-deep .mdc-text-field{border-top-left-radius:0!important;border-top-right-radius:0!important}\n"] }]
|
|
1312
|
+
}] });
|
|
1313
|
+
|
|
335
1314
|
class GisKomponentComponent {
|
|
336
1315
|
_profileService = inject(ProfileService);
|
|
1316
|
+
_undoRedo = inject(UndoRedoService);
|
|
337
1317
|
_http = inject(HttpClient);
|
|
338
1318
|
_layerHelper = inject(LayerHelperService);
|
|
1319
|
+
_layerErrorService = inject(LayerErrorService);
|
|
339
1320
|
identifier;
|
|
340
1321
|
settings;
|
|
1322
|
+
toolbarRef;
|
|
1323
|
+
activeObjectsRef;
|
|
341
1324
|
map;
|
|
342
1325
|
toolbarCollapsed = false;
|
|
343
1326
|
layerPanelCollapsed = false;
|
|
@@ -345,19 +1328,19 @@ class GisKomponentComponent {
|
|
|
345
1328
|
conflictsCollapsed = false;
|
|
346
1329
|
showConflicts = false;
|
|
347
1330
|
showObjects = false;
|
|
1331
|
+
showActiveObjects = false;
|
|
348
1332
|
profiles = [];
|
|
349
1333
|
selectedProfile;
|
|
1334
|
+
showToolbar = false;
|
|
350
1335
|
_komponentSettingsHelper = inject(KomponentSettingsHelperService);
|
|
351
1336
|
layers = [];
|
|
352
|
-
// activeObjects = [
|
|
353
|
-
// { name: 'Objekt A', size: '22 m2' },
|
|
354
|
-
// { name: 'Objekt B', size: '108,4 m2' }
|
|
355
|
-
// ];
|
|
356
1337
|
source = new VectorSource({ wrapX: false });
|
|
357
|
-
layer = new
|
|
1338
|
+
layer = new VectorLayer({ source: this.source });
|
|
358
1339
|
draw = new Draw({ source: this.source, type: 'Polygon' });
|
|
359
1340
|
move = new Translate({ layers: [this.layer] });
|
|
360
1341
|
mousePositionCtrl = new ol_control_mouse({ projection: 'EPSG:25832' });
|
|
1342
|
+
onMoveEndCheckZoomFunction = () => { };
|
|
1343
|
+
currentZoomLevel = 0;
|
|
361
1344
|
scaleCtrl = new ol_control_scale({
|
|
362
1345
|
units: 'metric',
|
|
363
1346
|
bar: true,
|
|
@@ -377,29 +1360,48 @@ class GisKomponentComponent {
|
|
|
377
1360
|
minZoom: profile.minZoom ?? undefined,
|
|
378
1361
|
projection: 'EPSG:25832'
|
|
379
1362
|
});
|
|
1363
|
+
this.currentZoomLevel = profile.zoom ?? 8;
|
|
380
1364
|
this._getCapabilitiesObject(profile).subscribe({
|
|
381
1365
|
next: capabilityObject => {
|
|
382
1366
|
const layers = [...profile.layerGroups.map((lg) => new OLLayerGroup({
|
|
383
1367
|
layers: lg.layers
|
|
384
1368
|
.filter(layer => layer.activeInSelector)
|
|
385
|
-
.
|
|
1369
|
+
.sort((a, b) => a.sortOrder - b.sortOrder)
|
|
1370
|
+
.map((l, index) => this._mapLayer(l, capabilityObject, 'EPSG:25832', lg.id, index)) // for now, we default to EPSG:25832.
|
|
386
1371
|
}))
|
|
387
1372
|
];
|
|
388
1373
|
this.map.setLayers(layers);
|
|
389
|
-
|
|
390
|
-
this._komponentSettingsHelper.loadThem(this.settings, profile.styleRepositoryWorkspace, profile.styleRepositoryGeoserver, this.map);
|
|
391
|
-
}
|
|
392
|
-
this._layerHelper.applyCachedLayersToDisplayInMap(this.map);
|
|
1374
|
+
this._layerHelper.applyCachedLayersToDisplayInMap(this.map, profile.id);
|
|
393
1375
|
}
|
|
394
1376
|
});
|
|
1377
|
+
const toolbarControl = new Control({
|
|
1378
|
+
element: this.toolbarRef.nativeElement
|
|
1379
|
+
});
|
|
1380
|
+
if (profile.showToolbar) {
|
|
1381
|
+
this.showToolbar = true;
|
|
1382
|
+
this.map.addControl(toolbarControl);
|
|
1383
|
+
}
|
|
1384
|
+
this.showActiveObjects = true;
|
|
1385
|
+
const activeObjectsControl = new Control({
|
|
1386
|
+
element: this.activeObjectsRef.nativeElement
|
|
1387
|
+
});
|
|
1388
|
+
this.map.addControl(activeObjectsControl);
|
|
395
1389
|
this._handleShowMouseCoordinates(profile.showCoordinates);
|
|
396
1390
|
this._handleShowScale(profile.showScale);
|
|
397
1391
|
this._setLogo(profile.logoUrl);
|
|
398
1392
|
this._setCopyRight(profile.copyrightMessage);
|
|
399
1393
|
this.map.setView(view);
|
|
1394
|
+
this.onMoveEndCheckZoomFunction = () => {
|
|
1395
|
+
this._checkZoom(this.map);
|
|
1396
|
+
};
|
|
1397
|
+
this.map.on('moveend', this.onMoveEndCheckZoomFunction);
|
|
1398
|
+
this._undoRedo.init();
|
|
400
1399
|
}
|
|
401
1400
|
});
|
|
402
1401
|
}
|
|
1402
|
+
_checkZoom(map) {
|
|
1403
|
+
this.currentZoomLevel = map.getView().getZoom() ?? this.currentZoomLevel;
|
|
1404
|
+
}
|
|
403
1405
|
_getCapabilitiesObject(profile) {
|
|
404
1406
|
// Find all WMTS-layers and get their base url's for retrieving the capabilities. Make sure every url is only called once by removing duplicates
|
|
405
1407
|
const getCapabilitiesUrls = [...new Set(profile.layerGroups.flatMap(lg => lg.layers).filter(lg => lg.layerType === 'WMTS').map(lg => lg.baseUrl))];
|
|
@@ -415,7 +1417,7 @@ class GisKomponentComponent {
|
|
|
415
1417
|
return obj;
|
|
416
1418
|
}, {})));
|
|
417
1419
|
}
|
|
418
|
-
_mapLayer(layer, wmtsOptions, projection, layerGroupDbId) {
|
|
1420
|
+
_mapLayer(layer, wmtsOptions, projection, layerGroupDbId, zIndex) {
|
|
419
1421
|
const params = {
|
|
420
1422
|
layers: layer.layers,
|
|
421
1423
|
};
|
|
@@ -432,7 +1434,7 @@ class GisKomponentComponent {
|
|
|
432
1434
|
source: new ImageWMS({
|
|
433
1435
|
url: layer.baseUrl,
|
|
434
1436
|
params
|
|
435
|
-
})
|
|
1437
|
+
})
|
|
436
1438
|
});
|
|
437
1439
|
break;
|
|
438
1440
|
case 'TILEWMS':
|
|
@@ -465,6 +1467,8 @@ class GisKomponentComponent {
|
|
|
465
1467
|
}
|
|
466
1468
|
this._layerHelper.setDbId(result, layer.id);
|
|
467
1469
|
this._layerHelper.setLayerGroupDbId(result, layerGroupDbId);
|
|
1470
|
+
this._layerErrorService.registerLayer(layer.id, result);
|
|
1471
|
+
result.setZIndex(zIndex);
|
|
468
1472
|
return result;
|
|
469
1473
|
}
|
|
470
1474
|
_setLogo(url) {
|
|
@@ -493,10 +1497,9 @@ class GisKomponentComponent {
|
|
|
493
1497
|
}
|
|
494
1498
|
ngOnInit() {
|
|
495
1499
|
this.profileSelected(this.identifier);
|
|
496
|
-
proj4.defs(
|
|
1500
|
+
proj4.defs('EPSG:25832', '+proj=utm +zone=32 +ellps=GRS80 +units=m +no_defs +type=crs');
|
|
497
1501
|
register(proj4);
|
|
498
|
-
|
|
499
|
-
this.map = new Map({
|
|
1502
|
+
this.map = new Map$1({
|
|
500
1503
|
target: 'map',
|
|
501
1504
|
});
|
|
502
1505
|
this.draw.setActive(false);
|
|
@@ -528,22 +1531,43 @@ class GisKomponentComponent {
|
|
|
528
1531
|
this.activeObjectsCollapsed = !this.activeObjectsCollapsed;
|
|
529
1532
|
}
|
|
530
1533
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: GisKomponentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
531
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: GisKomponentComponent, isStandalone: true, selector: "gis-komponent", inputs: { identifier: "identifier", settings: "settings" }, providers: [LayerHelperService], ngImport: i0, template: "
|
|
1534
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: GisKomponentComponent, isStandalone: true, selector: "gis-komponent", inputs: { identifier: "identifier", settings: "settings" }, providers: [LayerHelperService], viewQueries: [{ propertyName: "toolbarRef", first: true, predicate: ["toolbarRef"], descendants: true, static: true }, { propertyName: "activeObjectsRef", first: true, predicate: ["activeObjectsRef"], descendants: true, static: true }], ngImport: i0, template: "<lib-layer-selector [map]=\"map\" [profile]=\"selectedProfile\" [currentZoomLevel]=\"currentZoomLevel\"></lib-layer-selector>\n<span>Zoom: {{ currentZoomLevel}}</span>\n<div #activeObjectsRef>\n @if(showActiveObjects && settings) {\n <activeObjects [settings]=\"settings\"></activeObjects>\n }\n</div>\n<div #toolbarRef class=\"map-toolbar-container\">\n @if(showToolbar && settings) {\n <map-toolbox [map]=\"map\" \n [profile]=\"selectedProfile\"\n [settings]=\"settings\"\n [collapsed]=\"selectedProfile.toolbarCollapsed\" [WKTInputEnabled]=\"settings?.WKTInputEnabled\" [deleteEnabled]=\"settings?.deleteEnabled\" [showMeasureArea]=\"selectedProfile.showAreaMeasurement\" [showMeasureDistance]=\"selectedProfile.showDistanceMeasurement\"></map-toolbox>\n }\n</div>\n\n<div class=\"map-container\">\n <lib-map-search></lib-map-search>\n\n <!-- Objektvisning -->\n <!-- @if (showObjects) {\n <div class=\"object-panel\" [class.collapsed]=\"activeObjectsCollapsed\">\n <div class=\"header\" (click)=\"toggleActiveObjectsCollapsed()\">\n <span>Aktive objekter ({{activeObjects.length}})</span>\n <mat-icon>{{ activeObjectsCollapsed ? 'expand_more' : 'expand_less' }}</mat-icon>\n </div>\n @if (!activeObjectsCollapsed){\n <ul>\n <li *ngFor=\"let obj of activeObjects\">{{ obj.name }} ({{obj.size}})</li>\n </ul>\n }\n </div>\n } -->\n\n <!-- Konflikter -->\n @if (showConflicts) {\n\n <div class=\"conflict-panel\" [class.collapsed]=\"conflictsCollapsed\">\n <div class=\"header\">\n <span>Konflikter (3)</span>\n <mat-icon>{{ activeObjectsCollapsed ? 'expand_more' : 'expand_less' }}</mat-icon> -->\n </div>\n <mat-list>\n <mat-list-item>\n <span matListItemTitle>Lag #1</span>\n <span matListItemLine><mat-icon>highlight</mat-icon> Objekt #1</span>\n </mat-list-item>\n <mat-list-item>\n <span matListItemTitle>Lag #2 (svarer ikke)</span>\n </mat-list-item>\n <mat-list-item>\n <span matListItemTitle>Lag #3</span>\n <span matListItemLine><mat-icon>highlight</mat-icon> Objekt #1</span>\n <span matListItemLine><mat-icon>highlight</mat-icon> Objekt #2</span>\n </mat-list-item>\n </mat-list>\n </div>\n \n } \n\n <!-- Kort -->\n <div id=\"map\" class=\"map\"></div>\n \n\n</div>\n", styles: ["::ng-deep .lib-error-snackbar{background-color:#d32f2f;color:#fff;font-weight:500}::ng-deep .map-container{position:relative;height:85vh;width:100%}::ng-deep #map{width:100%;height:100%;position:absolute;inset:0}::ng-deep ::ng-deep .ol-logo{position:absolute;left:auto;right:3em;top:6.25em}::ng-deep ::ng-deep .ol-copyright{position:absolute;left:auto;right:calc(50vw - 90px);bottom:.5em}::ng-deep ::ng-deep .toolbar{position:absolute;top:10px;left:10px;background:#fff;padding:4px;border-radius:4px;display:flex;flex-direction:column;transition:width .3s;z-index:1000}::ng-deep ::ng-deep .toolbar.collapsed{width:40px;overflow:hidden}::ng-deep .object-panel{position:absolute;bottom:10px;left:10px;background:#fff;padding:8px;border-radius:4px;z-index:1000}::ng-deep .object-panel .header{display:flex;justify-content:space-between;padding:8px;cursor:pointer;background:#f0f0f0}::ng-deep .conflict-panel{position:absolute;bottom:10px;right:10px;background:#fff;padding:8px;border-radius:4px;z-index:1000}::ng-deep .conflict-panel .header{display:flex;justify-content:space-between;padding:8px;cursor:pointer;background:#f0f0f0}::ng-deep ::ng-deep .ol-zoom.ol-unselectable.ol-control{display:flex;flex-direction:column;position:absolute;top:2rem;left:1rem}::ng-deep ::ng-deep button.ol-zoom-in{font-size:2em;width:40px;height:40px;background-color:#fff;border:1px solid rgba(255,255,255,.8);box-shadow:#0000004d 0 1px 4px -1px;cursor:pointer;color:#666}::ng-deep button.ol-zoom-out{font-size:2em;width:40px;height:40px;background-color:#fff;border:1px solid rgba(255,255,255,.8);box-shadow:#0000004d 0 1px 4px -1px;cursor:pointer;color:#666}::ng-deep button.ol-rotate-reset{font-size:28px;width:35px;height:35px;background-color:#fff;border:1px solid rgba(255,255,255,.8);box-shadow:#0000004d 0 1px 4px -1px;cursor:pointer;color:#334155;margin-top:10px}::ng-deep .ol-scale-bar.ol-unselectable{position:absolute;bottom:1rem;left:1rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i2$1.MatList, selector: "mat-list", exportAs: ["matList"] }, { kind: "component", type: i2$1.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "directive", type: i2$1.MatListItemLine, selector: "[matListItemLine]" }, { kind: "directive", type: i2$1.MatListItemTitle, selector: "[matListItemTitle]" }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: LayerSelectorComponent, selector: "lib-layer-selector", inputs: ["map", "profile", "currentZoomLevel"] }, { kind: "component", type: MapSearchComponent, selector: "lib-map-search" }, { kind: "component", type: ToolboxComponent, selector: "map-toolbox", inputs: ["map", "showMeasureDistance", "showMeasureArea", "collapsed", "settings", "profile", "WKTInputEnabled", "deleteEnabled"] }, { kind: "component", type: ActiveObjectsComponent, selector: "activeObjects", inputs: ["settings"] }] });
|
|
532
1535
|
}
|
|
533
1536
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: GisKomponentComponent, decorators: [{
|
|
534
1537
|
type: Component,
|
|
535
|
-
args: [{ selector: 'gis-komponent', imports: [CommonModule, MatIconModule, MatListModule, MatSelectModule, LayerSelectorComponent], providers: [LayerHelperService], template: "
|
|
1538
|
+
args: [{ selector: 'gis-komponent', imports: [CommonModule, MatIconModule, MatListModule, MatSelectModule, LayerSelectorComponent, MapSearchComponent, ToolboxComponent, ActiveObjectsComponent], providers: [LayerHelperService], template: "<lib-layer-selector [map]=\"map\" [profile]=\"selectedProfile\" [currentZoomLevel]=\"currentZoomLevel\"></lib-layer-selector>\n<span>Zoom: {{ currentZoomLevel}}</span>\n<div #activeObjectsRef>\n @if(showActiveObjects && settings) {\n <activeObjects [settings]=\"settings\"></activeObjects>\n }\n</div>\n<div #toolbarRef class=\"map-toolbar-container\">\n @if(showToolbar && settings) {\n <map-toolbox [map]=\"map\" \n [profile]=\"selectedProfile\"\n [settings]=\"settings\"\n [collapsed]=\"selectedProfile.toolbarCollapsed\" [WKTInputEnabled]=\"settings?.WKTInputEnabled\" [deleteEnabled]=\"settings?.deleteEnabled\" [showMeasureArea]=\"selectedProfile.showAreaMeasurement\" [showMeasureDistance]=\"selectedProfile.showDistanceMeasurement\"></map-toolbox>\n }\n</div>\n\n<div class=\"map-container\">\n <lib-map-search></lib-map-search>\n\n <!-- Objektvisning -->\n <!-- @if (showObjects) {\n <div class=\"object-panel\" [class.collapsed]=\"activeObjectsCollapsed\">\n <div class=\"header\" (click)=\"toggleActiveObjectsCollapsed()\">\n <span>Aktive objekter ({{activeObjects.length}})</span>\n <mat-icon>{{ activeObjectsCollapsed ? 'expand_more' : 'expand_less' }}</mat-icon>\n </div>\n @if (!activeObjectsCollapsed){\n <ul>\n <li *ngFor=\"let obj of activeObjects\">{{ obj.name }} ({{obj.size}})</li>\n </ul>\n }\n </div>\n } -->\n\n <!-- Konflikter -->\n @if (showConflicts) {\n\n <div class=\"conflict-panel\" [class.collapsed]=\"conflictsCollapsed\">\n <div class=\"header\">\n <span>Konflikter (3)</span>\n <mat-icon>{{ activeObjectsCollapsed ? 'expand_more' : 'expand_less' }}</mat-icon> -->\n </div>\n <mat-list>\n <mat-list-item>\n <span matListItemTitle>Lag #1</span>\n <span matListItemLine><mat-icon>highlight</mat-icon> Objekt #1</span>\n </mat-list-item>\n <mat-list-item>\n <span matListItemTitle>Lag #2 (svarer ikke)</span>\n </mat-list-item>\n <mat-list-item>\n <span matListItemTitle>Lag #3</span>\n <span matListItemLine><mat-icon>highlight</mat-icon> Objekt #1</span>\n <span matListItemLine><mat-icon>highlight</mat-icon> Objekt #2</span>\n </mat-list-item>\n </mat-list>\n </div>\n \n } \n\n <!-- Kort -->\n <div id=\"map\" class=\"map\"></div>\n \n\n</div>\n", styles: ["::ng-deep .lib-error-snackbar{background-color:#d32f2f;color:#fff;font-weight:500}::ng-deep .map-container{position:relative;height:85vh;width:100%}::ng-deep #map{width:100%;height:100%;position:absolute;inset:0}::ng-deep ::ng-deep .ol-logo{position:absolute;left:auto;right:3em;top:6.25em}::ng-deep ::ng-deep .ol-copyright{position:absolute;left:auto;right:calc(50vw - 90px);bottom:.5em}::ng-deep ::ng-deep .toolbar{position:absolute;top:10px;left:10px;background:#fff;padding:4px;border-radius:4px;display:flex;flex-direction:column;transition:width .3s;z-index:1000}::ng-deep ::ng-deep .toolbar.collapsed{width:40px;overflow:hidden}::ng-deep .object-panel{position:absolute;bottom:10px;left:10px;background:#fff;padding:8px;border-radius:4px;z-index:1000}::ng-deep .object-panel .header{display:flex;justify-content:space-between;padding:8px;cursor:pointer;background:#f0f0f0}::ng-deep .conflict-panel{position:absolute;bottom:10px;right:10px;background:#fff;padding:8px;border-radius:4px;z-index:1000}::ng-deep .conflict-panel .header{display:flex;justify-content:space-between;padding:8px;cursor:pointer;background:#f0f0f0}::ng-deep ::ng-deep .ol-zoom.ol-unselectable.ol-control{display:flex;flex-direction:column;position:absolute;top:2rem;left:1rem}::ng-deep ::ng-deep button.ol-zoom-in{font-size:2em;width:40px;height:40px;background-color:#fff;border:1px solid rgba(255,255,255,.8);box-shadow:#0000004d 0 1px 4px -1px;cursor:pointer;color:#666}::ng-deep button.ol-zoom-out{font-size:2em;width:40px;height:40px;background-color:#fff;border:1px solid rgba(255,255,255,.8);box-shadow:#0000004d 0 1px 4px -1px;cursor:pointer;color:#666}::ng-deep button.ol-rotate-reset{font-size:28px;width:35px;height:35px;background-color:#fff;border:1px solid rgba(255,255,255,.8);box-shadow:#0000004d 0 1px 4px -1px;cursor:pointer;color:#334155;margin-top:10px}::ng-deep .ol-scale-bar.ol-unselectable{position:absolute;bottom:1rem;left:1rem}\n"] }]
|
|
536
1539
|
}], propDecorators: { identifier: [{
|
|
537
1540
|
type: Input
|
|
538
1541
|
}], settings: [{
|
|
539
1542
|
type: Input
|
|
1543
|
+
}], toolbarRef: [{
|
|
1544
|
+
type: ViewChild,
|
|
1545
|
+
args: ['toolbarRef', { static: true }]
|
|
1546
|
+
}], activeObjectsRef: [{
|
|
1547
|
+
type: ViewChild,
|
|
1548
|
+
args: ['activeObjectsRef', { static: true }]
|
|
540
1549
|
}] } });
|
|
541
1550
|
|
|
1551
|
+
const libErrorInterceptor = (req, next) => {
|
|
1552
|
+
const errorService = inject(LibErrorService);
|
|
1553
|
+
return next(req).pipe(catchError((error) => {
|
|
1554
|
+
errorService.log(error);
|
|
1555
|
+
errorService.show(error);
|
|
1556
|
+
return throwError(() => error); // Let the component decide if needed
|
|
1557
|
+
}));
|
|
1558
|
+
};
|
|
1559
|
+
|
|
542
1560
|
function provideLibrary(config) {
|
|
543
1561
|
return [
|
|
544
1562
|
{ provide: GISKOMPONENT_CONFIG, useValue: config }
|
|
545
1563
|
];
|
|
546
1564
|
}
|
|
1565
|
+
function provideLibraryHttpClient() {
|
|
1566
|
+
return [
|
|
1567
|
+
provideHttpClient(withInterceptors([libErrorInterceptor]))
|
|
1568
|
+
];
|
|
1569
|
+
}
|
|
1570
|
+
;
|
|
547
1571
|
|
|
548
1572
|
/*
|
|
549
1573
|
* Public API Surface of gis-komponent
|
|
@@ -553,5 +1577,5 @@ function provideLibrary(config) {
|
|
|
553
1577
|
* Generated bundle index. Do not edit.
|
|
554
1578
|
*/
|
|
555
1579
|
|
|
556
|
-
export { GisKomponentComponent, LayerHelperService, ProfileService, provideLibrary };
|
|
1580
|
+
export { GisKomponentComponent, LayerHelperService, LibErrorService, ProfileService, provideLibrary, provideLibraryHttpClient };
|
|
557
1581
|
//# sourceMappingURL=regionerne-gis-komponent.mjs.map
|