@fmidev/smartmet-alert-client 4.4.19 → 4.7.0-beta.0
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/.eslintignore +2 -14
- package/.github/workflows/test.yaml +26 -0
- package/.nvmrc +1 -0
- package/AGENTS.md +26 -0
- package/index.html +1 -1
- package/package.json +80 -22
- package/src/AlertClientVue.vue +160 -0
- package/src/App.vue +154 -296
- package/src/assets/img/ui/arrow-down.svg +4 -11
- package/src/assets/img/ui/arrow-up.svg +4 -11
- package/src/assets/img/ui/clear.svg +7 -21
- package/src/assets/img/ui/close.svg +4 -15
- package/src/assets/img/ui/toggle-selected.svg +5 -6
- package/src/assets/img/ui/toggle-unselected.svg +5 -6
- package/src/assets/img/warning/cold-weather.svg +3 -6
- package/src/assets/img/warning/flood-level-3.svg +4 -7
- package/src/assets/img/warning/forest-fire-weather.svg +2 -6
- package/src/assets/img/warning/grass-fire-weather.svg +2 -6
- package/src/assets/img/warning/hot-weather.svg +3 -6
- package/src/assets/img/warning/pedestrian-safety.svg +3 -7
- package/src/assets/img/warning/rain.svg +2 -7
- package/src/assets/img/warning/sea-icing.svg +2 -6
- package/src/assets/img/warning/sea-thunder-storm.svg +2 -5
- package/src/assets/img/warning/sea-water-height-high-water.svg +3 -8
- package/src/assets/img/warning/sea-water-height-shallow-water.svg +3 -7
- package/src/assets/img/warning/sea-wave-height.svg +4 -7
- package/src/assets/img/warning/sea-wind-legend.svg +2 -5
- package/src/assets/img/warning/sea-wind.svg +2 -5
- package/src/assets/img/warning/several.svg +2 -5
- package/src/assets/img/warning/thunder-storm.svg +2 -5
- package/src/assets/img/warning/traffic-weather.svg +2 -6
- package/src/assets/img/warning/uv-note.svg +2 -6
- package/src/assets/img/warning/wind.svg +2 -5
- package/src/components/AlertClient.vue +330 -251
- package/src/components/CollapsiblePanel.vue +281 -0
- package/src/components/DayLarge.vue +146 -110
- package/src/components/DaySmall.vue +97 -81
- package/src/components/Days.vue +229 -159
- package/src/components/DescriptionWarning.vue +63 -38
- package/src/components/GrayScaleToggle.vue +58 -54
- package/src/components/Legend.vue +102 -325
- package/src/components/MapLarge.vue +574 -351
- package/src/components/MapSmall.vue +137 -122
- package/src/components/PopupRow.vue +24 -12
- package/src/components/Region.vue +168 -118
- package/src/components/RegionWarning.vue +40 -33
- package/src/components/Regions.vue +189 -105
- package/src/components/Warning.vue +70 -45
- package/src/components/Warnings.vue +136 -72
- package/src/composables/useAlertClient.ts +360 -0
- package/src/composables/useConfig.ts +573 -0
- package/src/composables/useFields.ts +66 -0
- package/src/composables/useI18n.ts +62 -0
- package/src/composables/useKeyCodes.ts +16 -0
- package/src/composables/useMapPaths.ts +477 -0
- package/src/composables/useUtils.ts +683 -0
- package/src/composables/useWarningsProcessor.ts +1007 -0
- package/src/data/geometries.json +993 -0
- package/src/{main.js → main.ts} +1 -0
- package/src/mixins/geojsonsvg.d.ts +57 -0
- package/src/mixins/geojsonsvg.js +5 -3
- package/src/plugins/index.ts +5 -0
- package/src/scss/_utilities.scss +193 -0
- package/src/scss/constants.scss +2 -1
- package/src/scss/warningImages.scss +8 -3
- package/src/types/index.ts +509 -0
- package/src/vite-env.d.ts +23 -0
- package/src/vue.ts +41 -0
- package/svgo.config.js +45 -0
- package/tests/README.md +430 -0
- package/tests/fixtures/mockWarningData.ts +152 -0
- package/tests/integration/warning-flow.spec.ts +445 -0
- package/tests/setup.ts +41 -0
- package/tests/unit/components/AlertClient.spec.ts +701 -0
- package/tests/unit/components/DayLarge.spec.ts +348 -0
- package/tests/unit/components/DaySmall.spec.ts +352 -0
- package/tests/unit/components/Days.spec.ts +548 -0
- package/tests/unit/components/DescriptionWarning.spec.ts +385 -0
- package/tests/unit/components/GrayScaleToggle.spec.ts +318 -0
- package/tests/unit/components/Legend.spec.ts +295 -0
- package/tests/unit/components/MapLarge.spec.ts +448 -0
- package/tests/unit/components/MapSmall.spec.ts +367 -0
- package/tests/unit/components/PopupRow.spec.ts +270 -0
- package/tests/unit/components/Region.spec.ts +373 -0
- package/tests/unit/components/RegionWarning.snapshot.spec.ts +361 -0
- package/tests/unit/components/RegionWarning.spec.ts +381 -0
- package/tests/unit/components/Regions.spec.ts +503 -0
- package/tests/unit/components/Warning.snapshot.spec.ts +483 -0
- package/tests/unit/components/Warning.spec.ts +489 -0
- package/tests/unit/components/Warnings.spec.ts +343 -0
- package/tests/unit/components/__snapshots__/RegionWarning.snapshot.spec.ts.snap +41 -0
- package/tests/unit/components/__snapshots__/Warning.snapshot.spec.ts.snap +433 -0
- package/tests/unit/composables/useConfig.spec.ts +279 -0
- package/tests/unit/composables/useI18n.spec.ts +116 -0
- package/tests/unit/composables/useKeyCodes.spec.ts +27 -0
- package/tests/unit/composables/useUtils.spec.ts +213 -0
- package/tsconfig.json +43 -0
- package/tsconfig.node.json +11 -0
- package/vite.config.js +96 -26
- package/vitest.config.js +40 -0
- package/dist/favicon.ico +0 -0
- package/dist/index.dark.html +0 -20
- package/dist/index.en.html +0 -15
- package/dist/index.fi.html +0 -15
- package/dist/index.html +0 -15
- package/dist/index.js +0 -281
- package/dist/index.mjs +0 -281
- package/dist/index.mjs.map +0 -1
- package/dist/index.relative.html +0 -19
- package/dist/index.start.html +0 -20
- package/dist/index.sv.html +0 -15
- package/playwright.config.ts +0 -18
- package/public/index.relative.html +0 -19
- package/public/index.start.html +0 -20
- package/src/mixins/config.js +0 -1378
- package/src/mixins/fields.js +0 -26
- package/src/mixins/i18n.js +0 -25
- package/src/mixins/keycodes.js +0 -10
- package/src/mixins/panzoom.js +0 -900
- package/src/mixins/utils.js +0 -900
- package/src/plugins/index.js +0 -3
- package/test/snapshot.test.ts +0 -126
- package/vitest.config.ts +0 -6
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="map-large focus-ring" :class="theme" tabindex="0">
|
|
3
3
|
<div v-if="spinnerEnabled && loading" class="spinner-container text-center">
|
|
4
|
-
<
|
|
4
|
+
<div class="spinner-border" role="status">
|
|
5
|
+
<span class="visually-hidden"></span>
|
|
6
|
+
</div>
|
|
5
7
|
</div>
|
|
6
8
|
<div ref="dayMapLarge" class="day-map-large">
|
|
7
9
|
<svg
|
|
@@ -35,8 +37,8 @@
|
|
|
35
37
|
<path
|
|
36
38
|
v-for="path in seaBorders"
|
|
37
39
|
:id="path.key"
|
|
38
|
-
class="border-path"
|
|
39
40
|
:key="path.key"
|
|
41
|
+
class="border-path"
|
|
40
42
|
:stroke="strokeColor"
|
|
41
43
|
:stroke-width="path.strokeWidth"
|
|
42
44
|
:stroke-opacity="strokeOpacity"
|
|
@@ -134,17 +136,17 @@
|
|
|
134
136
|
:d="path.d"
|
|
135
137
|
fill-opacity="0"
|
|
136
138
|
style="cursor: pointer; pointer-events: none" />
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
139
|
+
<path
|
|
140
|
+
v-for="path in landBorders"
|
|
141
|
+
:id="path.key"
|
|
142
|
+
:key="path.key"
|
|
143
|
+
class="border-path"
|
|
144
|
+
:stroke="strokeColor"
|
|
145
|
+
:stroke-width="2 * Number(path.strokeWidth)"
|
|
146
|
+
:stroke-opacity="strokeOpacity"
|
|
147
|
+
:d="path.d"
|
|
148
|
+
fill-opacity="0"
|
|
149
|
+
style="cursor: pointer; pointer-events: none" />
|
|
148
150
|
</g>
|
|
149
151
|
<g v-if="!loading">
|
|
150
152
|
<path
|
|
@@ -255,56 +257,107 @@
|
|
|
255
257
|
</div>
|
|
256
258
|
</template>
|
|
257
259
|
|
|
258
|
-
<script>
|
|
259
|
-
import {
|
|
260
|
+
<script lang="ts">
|
|
261
|
+
import {
|
|
262
|
+
defineComponent,
|
|
263
|
+
onMounted,
|
|
264
|
+
onUnmounted,
|
|
265
|
+
ref,
|
|
266
|
+
computed,
|
|
267
|
+
toRef,
|
|
268
|
+
type PropType,
|
|
269
|
+
} from 'vue'
|
|
270
|
+
import Panzoom from '@panzoom/panzoom'
|
|
271
|
+
import type { PanzoomObject } from '@panzoom/panzoom'
|
|
260
272
|
|
|
261
|
-
import config from '../mixins/config'
|
|
262
|
-
import i18n from '../mixins/i18n'
|
|
263
|
-
import Panzoom from '../mixins/panzoom'
|
|
264
|
-
import utils from '../mixins/utils'
|
|
265
273
|
import PopupRow from './PopupRow.vue'
|
|
266
|
-
|
|
267
|
-
|
|
274
|
+
import { useMapPaths } from '@/composables/useMapPaths'
|
|
275
|
+
import { useI18n } from '@/composables/useI18n'
|
|
276
|
+
import { useConfig, MULTIPLE } from '@/composables/useConfig'
|
|
277
|
+
import { isClientSide } from '@/composables/useUtils'
|
|
278
|
+
import type {
|
|
279
|
+
DayRegions,
|
|
280
|
+
WarningsMap,
|
|
281
|
+
Theme,
|
|
282
|
+
RegionGeometry,
|
|
283
|
+
Language,
|
|
284
|
+
Severity,
|
|
285
|
+
} from '@/types'
|
|
286
|
+
|
|
287
|
+
interface IconData {
|
|
288
|
+
key: string
|
|
289
|
+
x: string
|
|
290
|
+
y: string
|
|
291
|
+
width: string | number
|
|
292
|
+
height: string | number
|
|
293
|
+
version: string
|
|
294
|
+
viewBox: string
|
|
295
|
+
geom: string
|
|
296
|
+
regionId?: string
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
interface PopupWarning {
|
|
300
|
+
id: string
|
|
301
|
+
type: string
|
|
302
|
+
severity: Severity
|
|
303
|
+
direction: number
|
|
304
|
+
text: string
|
|
305
|
+
interval: string
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
interface PanCoords {
|
|
309
|
+
x: number
|
|
310
|
+
y: number
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export default defineComponent({
|
|
268
314
|
name: 'MapLarge',
|
|
269
315
|
components: { PopupRow },
|
|
270
|
-
mixins: [config, i18n, Panzoom, utils],
|
|
271
316
|
props: {
|
|
272
317
|
index: {
|
|
273
|
-
type: Number
|
|
318
|
+
type: Number as PropType<number>,
|
|
319
|
+
default: 0,
|
|
274
320
|
},
|
|
275
321
|
input: {
|
|
276
|
-
type: Object
|
|
322
|
+
type: Object as PropType<DayRegions>,
|
|
323
|
+
default: () => ({}),
|
|
277
324
|
},
|
|
278
325
|
visibleWarnings: {
|
|
279
|
-
type: Array
|
|
326
|
+
type: Array as PropType<string[]>,
|
|
280
327
|
default: () => [],
|
|
281
328
|
},
|
|
282
329
|
warnings: {
|
|
283
|
-
type: Object
|
|
330
|
+
type: Object as PropType<WarningsMap | null>,
|
|
284
331
|
default: null,
|
|
285
332
|
},
|
|
286
333
|
geometryId: {
|
|
287
|
-
type: Number
|
|
334
|
+
type: Number as PropType<number>,
|
|
335
|
+
default: 2021,
|
|
288
336
|
},
|
|
289
337
|
loading: {
|
|
290
338
|
type: Boolean,
|
|
291
339
|
default: true,
|
|
292
340
|
},
|
|
293
341
|
theme: {
|
|
294
|
-
type: String
|
|
342
|
+
type: String as PropType<Theme | string>,
|
|
295
343
|
default: 'light-theme',
|
|
296
344
|
},
|
|
297
345
|
language: {
|
|
298
|
-
type: String
|
|
346
|
+
type: String as PropType<Language | string>,
|
|
347
|
+
default: 'fi',
|
|
299
348
|
},
|
|
300
349
|
spinnerEnabled: {
|
|
301
350
|
type: Boolean,
|
|
302
351
|
default: true,
|
|
303
352
|
},
|
|
304
353
|
},
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
354
|
+
emits: ['loaded'],
|
|
355
|
+
setup(props) {
|
|
356
|
+
// Window width tracking
|
|
357
|
+
const windowWidth = ref<number>(
|
|
358
|
+
typeof window !== 'undefined' ? window.innerWidth : 0
|
|
359
|
+
)
|
|
360
|
+
const updateWidth = (): void => {
|
|
308
361
|
windowWidth.value = window.innerWidth
|
|
309
362
|
}
|
|
310
363
|
onMounted(() => {
|
|
@@ -313,136 +366,261 @@ export default {
|
|
|
313
366
|
onUnmounted(() => {
|
|
314
367
|
window.removeEventListener('resize', updateWidth)
|
|
315
368
|
})
|
|
316
|
-
|
|
369
|
+
|
|
370
|
+
// Get config
|
|
371
|
+
const config = useConfig()
|
|
372
|
+
const {
|
|
373
|
+
geometries,
|
|
374
|
+
colors,
|
|
375
|
+
regionIds,
|
|
376
|
+
warningIcon,
|
|
377
|
+
panLimits,
|
|
378
|
+
maxMergedWeight,
|
|
379
|
+
coverageCriterion,
|
|
380
|
+
} = config
|
|
381
|
+
|
|
382
|
+
// Setup i18n
|
|
383
|
+
const languageRef = toRef(props, 'language')
|
|
384
|
+
const { t } = useI18n(languageRef)
|
|
385
|
+
|
|
386
|
+
// Setup refs for useMapPaths
|
|
387
|
+
const size = computed<'Large' | 'Small'>(() => 'Large')
|
|
388
|
+
const scale = ref<number>(1)
|
|
389
|
+
const strokeWidthComputed = computed<number>(
|
|
390
|
+
() => 1 - (scale.value - 1) / scale.value
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
const indexRef = toRef(props, 'index')
|
|
394
|
+
const inputRef = toRef(props, 'input')
|
|
395
|
+
const warningsRef = toRef(props, 'warnings')
|
|
396
|
+
const visibleWarningsRef = toRef(props, 'visibleWarnings')
|
|
397
|
+
const geometryIdRef = toRef(props, 'geometryId')
|
|
398
|
+
const themeRef = toRef(props, 'theme')
|
|
399
|
+
const loadingRef = toRef(props, 'loading')
|
|
400
|
+
|
|
401
|
+
// Setup map paths composable
|
|
402
|
+
const {
|
|
403
|
+
strokeColor,
|
|
404
|
+
bluePaths,
|
|
405
|
+
greenPaths,
|
|
406
|
+
yellowPaths,
|
|
407
|
+
orangePaths,
|
|
408
|
+
redPaths,
|
|
409
|
+
overlayPaths,
|
|
410
|
+
landBorders,
|
|
411
|
+
seaBorders,
|
|
412
|
+
yellowCoverages,
|
|
413
|
+
orangeCoverages,
|
|
414
|
+
redCoverages,
|
|
415
|
+
overlayCoverages,
|
|
416
|
+
coverageRegions,
|
|
417
|
+
coverageWarnings,
|
|
418
|
+
regionData,
|
|
419
|
+
regionVisualization,
|
|
420
|
+
} = useMapPaths({
|
|
421
|
+
size,
|
|
422
|
+
index: indexRef,
|
|
423
|
+
input: inputRef,
|
|
424
|
+
warnings: warningsRef,
|
|
425
|
+
visibleWarnings: visibleWarningsRef,
|
|
426
|
+
geometryId: geometryIdRef,
|
|
427
|
+
theme: themeRef,
|
|
428
|
+
loading: loadingRef,
|
|
429
|
+
strokeWidth: strokeWidthComputed,
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
return {
|
|
433
|
+
windowWidth,
|
|
434
|
+
t,
|
|
435
|
+
config,
|
|
436
|
+
geometries,
|
|
437
|
+
colors,
|
|
438
|
+
regionIds,
|
|
439
|
+
warningIcon,
|
|
440
|
+
panLimits,
|
|
441
|
+
maxMergedWeight,
|
|
442
|
+
coverageCriterion,
|
|
443
|
+
size,
|
|
444
|
+
scale,
|
|
445
|
+
strokeColor,
|
|
446
|
+
bluePaths,
|
|
447
|
+
greenPaths,
|
|
448
|
+
yellowPaths,
|
|
449
|
+
orangePaths,
|
|
450
|
+
redPaths,
|
|
451
|
+
overlayPaths,
|
|
452
|
+
landBorders,
|
|
453
|
+
seaBorders,
|
|
454
|
+
yellowCoverages,
|
|
455
|
+
orangeCoverages,
|
|
456
|
+
redCoverages,
|
|
457
|
+
overlayCoverages,
|
|
458
|
+
coverageRegions,
|
|
459
|
+
coverageWarnings,
|
|
460
|
+
regionData,
|
|
461
|
+
regionVisualization,
|
|
462
|
+
}
|
|
317
463
|
},
|
|
318
464
|
data() {
|
|
319
465
|
return {
|
|
320
|
-
warningsDate: '',
|
|
321
|
-
updated: '',
|
|
322
|
-
updatedDate: '',
|
|
323
|
-
atTime: '',
|
|
324
|
-
updatedTime: '',
|
|
325
|
-
dataProviderFirst: '',
|
|
326
|
-
dataProviderSecond: '',
|
|
327
|
-
mapText: '',
|
|
328
|
-
actionStarted: false,
|
|
329
|
-
dragging: false,
|
|
330
|
-
showTooltip: false,
|
|
331
|
-
tooltipX: 0,
|
|
332
|
-
tooltipY: 0,
|
|
466
|
+
warningsDate: '' as string,
|
|
467
|
+
updated: '' as string,
|
|
468
|
+
updatedDate: '' as string,
|
|
469
|
+
atTime: '' as string,
|
|
470
|
+
updatedTime: '' as string,
|
|
471
|
+
dataProviderFirst: '' as string,
|
|
472
|
+
dataProviderSecond: '' as string,
|
|
473
|
+
mapText: '' as string,
|
|
474
|
+
actionStarted: false as boolean,
|
|
475
|
+
dragging: false as boolean,
|
|
476
|
+
showTooltip: false as boolean,
|
|
477
|
+
tooltipX: 0 as number,
|
|
478
|
+
tooltipY: 0 as number,
|
|
333
479
|
pan: {
|
|
334
480
|
x: 0,
|
|
335
481
|
y: 0,
|
|
336
|
-
},
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
coverageWarnings: [],
|
|
343
|
-
strokeOpacity: '0.5',
|
|
482
|
+
} as PanCoords,
|
|
483
|
+
popupRegion: {} as Partial<RegionGeometry>,
|
|
484
|
+
popupLevel: '' as string,
|
|
485
|
+
popupWarnings: [] as PopupWarning[],
|
|
486
|
+
strokeOpacity: '0.5' as string,
|
|
487
|
+
panzoom: null as PanzoomObject | null,
|
|
344
488
|
}
|
|
345
489
|
},
|
|
346
490
|
computed: {
|
|
347
|
-
moveStep() {
|
|
491
|
+
moveStep(): number {
|
|
348
492
|
return 25
|
|
349
493
|
},
|
|
350
|
-
minIconDistSqr() {
|
|
494
|
+
minIconDistSqr(): number {
|
|
351
495
|
return 500
|
|
352
496
|
},
|
|
353
|
-
iconDistStep() {
|
|
497
|
+
iconDistStep(): number {
|
|
354
498
|
return 10
|
|
355
499
|
},
|
|
356
|
-
iconMaxIter() {
|
|
500
|
+
iconMaxIter(): number {
|
|
357
501
|
return 40
|
|
358
502
|
},
|
|
359
|
-
zoomInText() {
|
|
503
|
+
zoomInText(): string {
|
|
360
504
|
return this.t('zoomIn')
|
|
361
505
|
},
|
|
362
|
-
zoomOutText() {
|
|
506
|
+
zoomOutText(): string {
|
|
363
507
|
return this.t('zoomOut')
|
|
364
508
|
},
|
|
365
|
-
moveText() {
|
|
509
|
+
moveText(): string {
|
|
366
510
|
return this.t('moveMap')
|
|
367
511
|
},
|
|
368
|
-
tooltipStyle() {
|
|
512
|
+
tooltipStyle(): string {
|
|
369
513
|
return `left: ${this.tooltipX}px; top: ${this.tooltipY}px`
|
|
370
514
|
},
|
|
371
|
-
|
|
372
|
-
return 'Large'
|
|
373
|
-
},
|
|
374
|
-
strokeWidth() {
|
|
515
|
+
strokeWidth(): string {
|
|
375
516
|
return String(1 - (this.scale - 1) / this.scale)
|
|
376
517
|
},
|
|
377
|
-
iconSize() {
|
|
518
|
+
iconSize(): number {
|
|
378
519
|
return 28 - 4 * this.scale
|
|
379
520
|
},
|
|
380
|
-
maxWarningIcons() {
|
|
521
|
+
maxWarningIcons(): number {
|
|
381
522
|
return this.scale + 1
|
|
382
523
|
},
|
|
383
|
-
icons() {
|
|
384
|
-
const data = []
|
|
524
|
+
icons(): IconData[] {
|
|
525
|
+
const data: IconData[] = []
|
|
385
526
|
const warnings = this.warnings
|
|
386
527
|
const maxWarningIcons = this.maxWarningIcons
|
|
387
|
-
this.
|
|
528
|
+
const geometriesData = this.geometries as Record<
|
|
529
|
+
string,
|
|
530
|
+
Record<string, RegionGeometry>
|
|
531
|
+
>
|
|
532
|
+
const maxMergedWeightVal = this.maxMergedWeight as number
|
|
533
|
+
|
|
534
|
+
this.regionIds.forEach((regionId: string) => {
|
|
388
535
|
const region = this.regionData(regionId)
|
|
389
|
-
const geometry =
|
|
536
|
+
const geometry = geometriesData?.[this.geometryId]?.[regionId]
|
|
390
537
|
if (
|
|
538
|
+
geometry &&
|
|
391
539
|
region != null &&
|
|
392
540
|
geometry.children.length === 0 &&
|
|
393
541
|
(!this.mergedRegions.has(regionId) ||
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
542
|
+
(geometry.weight > maxMergedWeightVal &&
|
|
543
|
+
region?.warnings?.filter((warning: { type: string }) =>
|
|
544
|
+
this.visibleWarnings.includes(warning.type)
|
|
545
|
+
).length === 1 &&
|
|
546
|
+
!(
|
|
547
|
+
geometry?.parent?.length &&
|
|
548
|
+
this.regionData(geometry.parent)?.warnings?.some(
|
|
549
|
+
(warning: { type: string }) =>
|
|
550
|
+
this.visibleWarnings.includes(warning.type)
|
|
551
|
+
)
|
|
552
|
+
)))
|
|
398
553
|
) {
|
|
399
|
-
const iconSizes = []
|
|
400
|
-
const aspectRatios = []
|
|
401
|
-
const keys = []
|
|
402
|
-
const geoms = []
|
|
554
|
+
const iconSizes: [number, number][] = []
|
|
555
|
+
const aspectRatios: [number, number][] = []
|
|
556
|
+
const keys: string[] = []
|
|
557
|
+
const geoms: string[] = []
|
|
403
558
|
region.warnings
|
|
404
|
-
.filter(
|
|
405
|
-
warning
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
559
|
+
.filter(
|
|
560
|
+
(warning: { type: string; coverage: number }) =>
|
|
561
|
+
this.visibleWarnings.includes(warning.type) &&
|
|
562
|
+
warning.coverage === 100
|
|
563
|
+
)
|
|
564
|
+
.forEach(
|
|
565
|
+
(
|
|
566
|
+
regionWarning: { identifiers: string[] },
|
|
567
|
+
_index: number,
|
|
568
|
+
regionWarnings: { identifiers: string[] }[]
|
|
569
|
+
) => {
|
|
570
|
+
const identifier = regionWarning.identifiers.find(
|
|
571
|
+
(id: string) =>
|
|
572
|
+
warnings?.[id] && warnings[id].covRegions.size === 0
|
|
573
|
+
)
|
|
574
|
+
if (identifier && iconSizes.length < maxWarningIcons) {
|
|
575
|
+
const warningData = warnings![identifier]
|
|
576
|
+
const icon =
|
|
577
|
+
iconSizes.length === maxWarningIcons - 1 &&
|
|
578
|
+
regionWarnings.length > maxWarningIcons
|
|
579
|
+
? this.warningIcon({ type: MULTIPLE, severity: 0 })
|
|
580
|
+
: warningData
|
|
581
|
+
? this.warningIcon(warningData)
|
|
582
|
+
: null
|
|
583
|
+
if (!icon) return
|
|
584
|
+
const iconScale = icon.scale ? icon.scale : 1
|
|
585
|
+
const width =
|
|
586
|
+
(iconScale * icon.aspectRatio[0] * this.iconSize) /
|
|
587
|
+
icon.aspectRatio[1]
|
|
588
|
+
const height = iconScale * this.iconSize + 6
|
|
589
|
+
iconSizes.push([width, height])
|
|
590
|
+
aspectRatios.push(icon.aspectRatio)
|
|
591
|
+
geoms.push(icon.geom || '')
|
|
592
|
+
keys.push(`${regionId}-${identifier}`)
|
|
593
|
+
}
|
|
425
594
|
}
|
|
426
|
-
|
|
595
|
+
)
|
|
596
|
+
const regionGeom = geometriesData[this.geometryId]?.[regionId] as
|
|
597
|
+
| RegionGeometry
|
|
598
|
+
| undefined
|
|
599
|
+
if (!regionGeom) return
|
|
600
|
+
const lastIconWidth = iconSizes[iconSizes.length - 1]?.[0] ?? 0
|
|
427
601
|
let offsetX =
|
|
428
|
-
iconSizes.length > 0 &&
|
|
429
|
-
this.geometries[this.geometryId][regionId].align === 'right'
|
|
602
|
+
iconSizes.length > 0 && regionGeom.align === 'right'
|
|
430
603
|
? -iconSizes.reduce(
|
|
431
604
|
(acc, iconSize) => acc + iconSize[0],
|
|
432
|
-
-
|
|
605
|
+
-lastIconWidth / 2
|
|
433
606
|
)
|
|
434
607
|
: -iconSizes.reduce((acc, iconSize) => acc + iconSize[0], 0) / 2
|
|
435
|
-
const coords =
|
|
436
|
-
|
|
608
|
+
const coords = regionGeom.center
|
|
609
|
+
if (!coords) return
|
|
610
|
+
iconSizes.forEach((iconSize, idx) => {
|
|
611
|
+
const aspectRatio = aspectRatios[idx]
|
|
612
|
+
const geom = geoms[idx]
|
|
613
|
+
const key = keys[idx]
|
|
614
|
+
if (!aspectRatio || geom == null || !key) return
|
|
437
615
|
data.push({
|
|
438
|
-
key
|
|
616
|
+
key,
|
|
439
617
|
x: `${coords[0] + offsetX}px`,
|
|
440
618
|
y: `${coords[1] - iconSize[1] / 2}px`,
|
|
441
619
|
width: `${iconSize[0]}px`,
|
|
442
620
|
height: `${iconSize[1]}px`,
|
|
443
621
|
version: '1.1',
|
|
444
|
-
viewBox: `0 0 ${
|
|
445
|
-
geom
|
|
622
|
+
viewBox: `0 0 ${aspectRatio[0]} ${aspectRatio[1]}`,
|
|
623
|
+
geom,
|
|
446
624
|
regionId,
|
|
447
625
|
})
|
|
448
626
|
offsetX += iconSize[0]
|
|
@@ -451,95 +629,129 @@ export default {
|
|
|
451
629
|
})
|
|
452
630
|
return data
|
|
453
631
|
},
|
|
454
|
-
coverageIcons() {
|
|
632
|
+
coverageIcons(): IconData[] {
|
|
455
633
|
const warnings = this.warnings
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
warning
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
!this.validIconLocation(reference, warningId) &&
|
|
469
|
-
iterIndex < this.iconMaxIter
|
|
634
|
+
|
|
635
|
+
return this.coverageWarnings.reduce(
|
|
636
|
+
(iconData: IconData[], warningId: string) => {
|
|
637
|
+
const warning = warnings?.[warningId]
|
|
638
|
+
const coverageLarge = warning?.coveragesLarge[0]
|
|
639
|
+
const baseReference = coverageLarge?.reference
|
|
640
|
+
if (
|
|
641
|
+
warning &&
|
|
642
|
+
this.visibleWarnings.includes(warning.type) &&
|
|
643
|
+
warning.coveragesLarge.length > 0 &&
|
|
644
|
+
baseReference &&
|
|
645
|
+
baseReference.length === 2
|
|
470
646
|
) {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
reference = [
|
|
475
|
-
warning.coveragesLarge[0].reference[0] + radius * Math.cos(angle),
|
|
476
|
-
warning.coveragesLarge[0].reference[1] + radius * Math.sin(angle),
|
|
647
|
+
let reference: [number, number] = [
|
|
648
|
+
baseReference[0],
|
|
649
|
+
baseReference[1],
|
|
477
650
|
]
|
|
651
|
+
let iterIndex = 0
|
|
652
|
+
let radius: number
|
|
653
|
+
let angle: number
|
|
654
|
+
// Prevent too close warning symbols
|
|
655
|
+
while (
|
|
656
|
+
!this.validIconLocation(reference, warningId) &&
|
|
657
|
+
iterIndex < this.iconMaxIter
|
|
658
|
+
) {
|
|
659
|
+
angle = 0.25 * Math.PI * iterIndex
|
|
660
|
+
iterIndex++
|
|
661
|
+
radius = Math.ceil(iterIndex / 8) * this.iconDistStep
|
|
662
|
+
reference = [
|
|
663
|
+
baseReference[0] + radius * Math.cos(angle),
|
|
664
|
+
baseReference[1] + radius * Math.sin(angle),
|
|
665
|
+
]
|
|
666
|
+
}
|
|
667
|
+
if (iterIndex >= this.iconMaxIter) {
|
|
668
|
+
reference = [baseReference[0], baseReference[1]]
|
|
669
|
+
}
|
|
670
|
+
const icon = this.warningIcon(warning)
|
|
671
|
+
const iconScale = icon.scale ? icon.scale : 1
|
|
672
|
+
const width =
|
|
673
|
+
(iconScale * icon.aspectRatio[0] * this.iconSize) /
|
|
674
|
+
icon.aspectRatio[1]
|
|
675
|
+
const height = iconScale * this.iconSize
|
|
676
|
+
iconData.push({
|
|
677
|
+
key: warningId + Math.random(),
|
|
678
|
+
x: `${reference[0] - width / 2}px`,
|
|
679
|
+
y: `${reference[1] - height / 2}px`,
|
|
680
|
+
width,
|
|
681
|
+
height,
|
|
682
|
+
version: '1.1',
|
|
683
|
+
viewBox: `0 0 ${icon.aspectRatio[0]} ${icon.aspectRatio[1]}`,
|
|
684
|
+
geom: icon.geom || '',
|
|
685
|
+
})
|
|
478
686
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
const scale = icon.scale ? icon.scale : 1
|
|
484
|
-
const width =
|
|
485
|
-
(scale * icon.aspectRatio[0] * this.iconSize) / icon.aspectRatio[1]
|
|
486
|
-
const height = scale * this.iconSize
|
|
487
|
-
iconData.push({
|
|
488
|
-
key: warningId + Math.random(),
|
|
489
|
-
x: `${reference[0] - width / 2}px`,
|
|
490
|
-
y: `${reference[1] - height / 2}px`,
|
|
491
|
-
width,
|
|
492
|
-
height,
|
|
493
|
-
version: '1.1',
|
|
494
|
-
viewBox: `0 0 ${icon.aspectRatio[0]} ${icon.aspectRatio[1]}`,
|
|
495
|
-
geom: icon.geom,
|
|
496
|
-
})
|
|
497
|
-
}
|
|
498
|
-
return iconData
|
|
499
|
-
}, [])
|
|
687
|
+
return iconData
|
|
688
|
+
},
|
|
689
|
+
[]
|
|
690
|
+
)
|
|
500
691
|
},
|
|
501
|
-
regionTitle() {
|
|
502
|
-
return this.t(this.popupRegion.name)
|
|
692
|
+
regionTitle(): string {
|
|
693
|
+
return this.t(this.popupRegion.name || '')
|
|
503
694
|
},
|
|
504
|
-
regionSets() {
|
|
505
|
-
const map = new Map()
|
|
695
|
+
regionSets(): Map<string, Set<string>> {
|
|
696
|
+
const map = new Map<string, Set<string>>()
|
|
506
697
|
const warnings = this.warnings
|
|
698
|
+
const geometriesData = this.geometries as Record<
|
|
699
|
+
string,
|
|
700
|
+
Record<string, RegionGeometry>
|
|
701
|
+
>
|
|
702
|
+
|
|
703
|
+
if (!this.input?.land) return map
|
|
704
|
+
|
|
705
|
+
const geomYear = geometriesData[this.geometryId]
|
|
507
706
|
this.input.land
|
|
508
707
|
.filter(
|
|
509
|
-
(regionItem) =>
|
|
510
|
-
|
|
511
|
-
0
|
|
708
|
+
(regionItem: { key: string }) =>
|
|
709
|
+
(geomYear?.[regionItem.key]?.neighbours?.length ?? 0) > 0
|
|
512
710
|
)
|
|
513
|
-
.forEach(
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
711
|
+
.forEach(
|
|
712
|
+
(regionItem: {
|
|
713
|
+
key: string
|
|
714
|
+
warnings: Array<{ type: string; identifiers: string[] }>
|
|
715
|
+
}) => {
|
|
716
|
+
const serialized = regionItem.warnings.reduce(
|
|
717
|
+
(reduced: string, warning) => {
|
|
718
|
+
if (!this.visibleWarnings.includes(warning.type)) {
|
|
719
|
+
return reduced
|
|
720
|
+
}
|
|
721
|
+
const warningIdentifier = warning.identifiers.find(
|
|
722
|
+
(identifier) => {
|
|
723
|
+
const warningById = warnings?.[identifier]
|
|
724
|
+
return (
|
|
725
|
+
warningById &&
|
|
726
|
+
Object.keys(warningById.regions).length >
|
|
727
|
+
warningById.covRegions.size
|
|
728
|
+
)
|
|
729
|
+
}
|
|
730
|
+
)
|
|
731
|
+
if (warningIdentifier == null) {
|
|
732
|
+
return reduced
|
|
733
|
+
}
|
|
734
|
+
const w = warnings?.[warningIdentifier]
|
|
735
|
+
if (!w) return reduced
|
|
736
|
+
return `${reduced}:${w.type}:${w.severity}:${w.value}:${w.direction}`
|
|
737
|
+
},
|
|
738
|
+
''
|
|
739
|
+
)
|
|
740
|
+
if (serialized) {
|
|
741
|
+
const set = map.has(serialized)
|
|
742
|
+
? map.get(serialized)!
|
|
743
|
+
: new Set<string>()
|
|
744
|
+
set.add(regionItem.key)
|
|
745
|
+
map.set(serialized, set)
|
|
527
746
|
}
|
|
528
|
-
const w = warnings[warningIdentifier]
|
|
529
|
-
return `${reduced}:${w.type}:${w.severity}:${w.value}:${w.direction}`
|
|
530
|
-
}, '')
|
|
531
|
-
if (serialized) {
|
|
532
|
-
const set = map.has(serialized) ? map.get(serialized) : new Set()
|
|
533
|
-
set.add(regionItem.key)
|
|
534
|
-
map.set(serialized, set)
|
|
535
747
|
}
|
|
536
|
-
|
|
748
|
+
)
|
|
537
749
|
return map
|
|
538
750
|
},
|
|
539
|
-
networks() {
|
|
540
|
-
let allNetworks = []
|
|
751
|
+
networks(): string[][] {
|
|
752
|
+
let allNetworks: Set<string>[] = []
|
|
541
753
|
this.regionSets.forEach((regionSet) => {
|
|
542
|
-
const networks = []
|
|
754
|
+
const networks: Set<string>[] = []
|
|
543
755
|
regionSet.forEach((region) => {
|
|
544
756
|
networks.push(new Set([region]))
|
|
545
757
|
})
|
|
@@ -547,7 +759,7 @@ export default {
|
|
|
547
759
|
while (this.mergeNetworks(networks)) {}
|
|
548
760
|
allNetworks = allNetworks.concat(networks)
|
|
549
761
|
})
|
|
550
|
-
const arrayNetworks = []
|
|
762
|
+
const arrayNetworks: string[][] = []
|
|
551
763
|
allNetworks.forEach((network) => {
|
|
552
764
|
if (network.size > 1) {
|
|
553
765
|
arrayNetworks.push(Array.from(network.keys()))
|
|
@@ -555,52 +767,63 @@ export default {
|
|
|
555
767
|
})
|
|
556
768
|
return arrayNetworks
|
|
557
769
|
},
|
|
558
|
-
networkCenters() {
|
|
770
|
+
networkCenters(): [number, number][] {
|
|
771
|
+
const geometriesData = this.geometries as Record<
|
|
772
|
+
string,
|
|
773
|
+
Record<string, RegionGeometry>
|
|
774
|
+
>
|
|
775
|
+
const geomYear = geometriesData[this.geometryId]
|
|
776
|
+
|
|
559
777
|
return this.networks.map((network) => {
|
|
560
778
|
const arrayNetwork = Array.from(network)
|
|
561
|
-
const weightSum = arrayNetwork.reduce(
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
)
|
|
779
|
+
const weightSum = arrayNetwork.reduce((sum, region) => {
|
|
780
|
+
const geom = geomYear?.[region]
|
|
781
|
+
return sum + (geom?.weight ?? 0)
|
|
782
|
+
}, 0)
|
|
566
783
|
return arrayNetwork
|
|
567
784
|
.reduce(
|
|
568
785
|
(sum, region) => {
|
|
569
|
-
const geom =
|
|
570
|
-
return sum
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
786
|
+
const geom = geomYear?.[region]
|
|
787
|
+
if (!geom?.center) return sum
|
|
788
|
+
return [
|
|
789
|
+
sum[0] + geom.weight * geom.center[0],
|
|
790
|
+
sum[1] + geom.weight * geom.center[1],
|
|
791
|
+
] as [number, number]
|
|
574
792
|
},
|
|
575
|
-
[0, 0]
|
|
793
|
+
[0, 0] as [number, number]
|
|
576
794
|
)
|
|
577
|
-
.map((weightedSumByIndex) => weightedSumByIndex / weightSum)
|
|
795
|
+
.map((weightedSumByIndex) => weightedSumByIndex / weightSum) as [
|
|
796
|
+
number,
|
|
797
|
+
number,
|
|
798
|
+
]
|
|
578
799
|
})
|
|
579
800
|
},
|
|
580
|
-
networkReps() {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
801
|
+
networkReps(): string[] {
|
|
802
|
+
const geometriesData = this.geometries as Record<
|
|
803
|
+
string,
|
|
804
|
+
Record<string, RegionGeometry>
|
|
805
|
+
>
|
|
806
|
+
const geomYear = geometriesData[this.geometryId]
|
|
807
|
+
|
|
808
|
+
return this.networks
|
|
809
|
+
.map((network, networkIndex) => {
|
|
810
|
+
const distances = network.map((region) => {
|
|
811
|
+
const geom = geomYear?.[region]
|
|
812
|
+
if (!geom?.center) return Infinity
|
|
813
|
+
const networkCenter = this.networkCenters[networkIndex]
|
|
814
|
+
if (!networkCenter) return Infinity
|
|
815
|
+
return (
|
|
816
|
+
((geom.center[0] - networkCenter[0]) ** 2 +
|
|
817
|
+
(geom.center[1] - networkCenter[1]) ** 2) /
|
|
818
|
+
geom.weight
|
|
598
819
|
)
|
|
599
|
-
|
|
600
|
-
|
|
820
|
+
})
|
|
821
|
+
return network[this.indexOfSmallest(distances)]
|
|
822
|
+
})
|
|
823
|
+
.filter((rep): rep is string => rep !== undefined)
|
|
601
824
|
},
|
|
602
|
-
mergedRegions() {
|
|
603
|
-
const merged = new Set()
|
|
825
|
+
mergedRegions(): Set<string> {
|
|
826
|
+
const merged = new Set<string>()
|
|
604
827
|
this.networks.forEach((network, index) => {
|
|
605
828
|
network.forEach((region) => {
|
|
606
829
|
if (region !== this.networkReps[index]) {
|
|
@@ -612,7 +835,7 @@ export default {
|
|
|
612
835
|
},
|
|
613
836
|
},
|
|
614
837
|
watch: {
|
|
615
|
-
scale() {
|
|
838
|
+
scale(): void {
|
|
616
839
|
if (this.panzoom != null) {
|
|
617
840
|
if (this.scale === 1) {
|
|
618
841
|
this.panzoom.setOptions({
|
|
@@ -628,27 +851,30 @@ export default {
|
|
|
628
851
|
}
|
|
629
852
|
}
|
|
630
853
|
},
|
|
631
|
-
input() {
|
|
854
|
+
input(): void {
|
|
632
855
|
this.coverageRegions = {}
|
|
633
856
|
this.coverageWarnings = []
|
|
634
857
|
},
|
|
635
|
-
warnings() {
|
|
858
|
+
warnings(): void {
|
|
636
859
|
this.showTooltip = false
|
|
637
860
|
},
|
|
638
|
-
visibleWarnings() {
|
|
861
|
+
visibleWarnings(): void {
|
|
639
862
|
this.showTooltip = false
|
|
640
863
|
},
|
|
641
|
-
windowWidth() {
|
|
864
|
+
windowWidth(): void {
|
|
642
865
|
this.showTooltip = false
|
|
643
|
-
|
|
866
|
+
const zoomButton = this.$refs.zoomButton as HTMLButtonElement | undefined
|
|
867
|
+
if (zoomButton?.clientHeight === 0 && this.scale > 1) {
|
|
644
868
|
this.scale = 1
|
|
645
869
|
}
|
|
646
870
|
},
|
|
647
871
|
},
|
|
648
872
|
mounted() {
|
|
649
|
-
if (
|
|
650
|
-
const finlandLarge = this.$el.querySelector(
|
|
651
|
-
|
|
873
|
+
if (isClientSide()) {
|
|
874
|
+
const finlandLarge = this.$el.querySelector(
|
|
875
|
+
'svg#finland-large'
|
|
876
|
+
) as SVGSVGElement | null
|
|
877
|
+
if (finlandLarge && this.isAttached(finlandLarge)) {
|
|
652
878
|
this.panzoom = Panzoom(finlandLarge, {
|
|
653
879
|
disableZoom: true,
|
|
654
880
|
panOnlyWhenZoomed: true,
|
|
@@ -659,7 +885,7 @@ export default {
|
|
|
659
885
|
touchAction: '',
|
|
660
886
|
})
|
|
661
887
|
finlandLarge.addEventListener('panzoomzoom', () => {
|
|
662
|
-
this.scale = this.panzoom
|
|
888
|
+
this.scale = this.panzoom!.getScale()
|
|
663
889
|
this.showTooltip = false
|
|
664
890
|
})
|
|
665
891
|
finlandLarge.addEventListener('panzoompan', (event) => {
|
|
@@ -667,12 +893,12 @@ export default {
|
|
|
667
893
|
if (!this.actionStarted) {
|
|
668
894
|
return
|
|
669
895
|
}
|
|
670
|
-
const eventDetail = event.detail
|
|
896
|
+
const eventDetail = (event as CustomEvent).detail as PanCoords | null
|
|
671
897
|
if (eventDetail == null) {
|
|
672
898
|
return
|
|
673
899
|
}
|
|
674
900
|
let panned = false
|
|
675
|
-
;['x', 'y'].forEach((axis) => {
|
|
901
|
+
;(['x', 'y'] as const).forEach((axis) => {
|
|
676
902
|
if (eventDetail[axis] !== this.pan[axis]) {
|
|
677
903
|
this.pan[axis] = eventDetail[axis]
|
|
678
904
|
panned = true
|
|
@@ -703,70 +929,53 @@ export default {
|
|
|
703
929
|
}
|
|
704
930
|
},
|
|
705
931
|
methods: {
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
d: visualization.visible ? visualization.geom.pathLarge : '',
|
|
725
|
-
opacity: '1',
|
|
726
|
-
dataRegion: regionId,
|
|
727
|
-
dataSeverity: visualization.severity,
|
|
728
|
-
strokeWidth:
|
|
729
|
-
this.geometries[this.geometryId][regionId].type === 'sea' &&
|
|
730
|
-
this.geometries[this.geometryId][regionId].subType !== 'lake'
|
|
731
|
-
? this.strokeWidth
|
|
732
|
-
: 0,
|
|
733
|
-
})
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
return regions
|
|
737
|
-
}, [])
|
|
738
|
-
},
|
|
739
|
-
regionClicked(event) {
|
|
740
|
-
const regionId = event.target.getAttribute('data-region')
|
|
741
|
-
let severity = Number(event.target.getAttribute('data-severity'))
|
|
742
|
-
this.popupRegion = this.geometries[this.geometryId][regionId]
|
|
743
|
-
const region = this.input[this.popupRegion.type].find(
|
|
744
|
-
(regionWarning) => regionWarning.key === regionId
|
|
932
|
+
regionClicked(event: MouseEvent): void {
|
|
933
|
+
const target = event.target as SVGPathElement
|
|
934
|
+
const regionId = target.getAttribute('data-region')
|
|
935
|
+
if (!regionId) return
|
|
936
|
+
|
|
937
|
+
let severity = Number(target.getAttribute('data-severity'))
|
|
938
|
+
const geometriesData = this.geometries as Record<
|
|
939
|
+
string,
|
|
940
|
+
Record<string, RegionGeometry>
|
|
941
|
+
>
|
|
942
|
+
const coverageCriterionVal = this.coverageCriterion as number
|
|
943
|
+
|
|
944
|
+
const regionGeom = geometriesData[this.geometryId]?.[regionId]
|
|
945
|
+
if (!regionGeom) return
|
|
946
|
+
this.popupRegion = regionGeom
|
|
947
|
+
const regionType = this.popupRegion.type as 'land' | 'sea'
|
|
948
|
+
const region = this.input?.[regionType]?.find(
|
|
949
|
+
(regionWarning: { key: string }) => regionWarning.key === regionId
|
|
745
950
|
)
|
|
746
|
-
let
|
|
951
|
+
let popupWarningsData: PopupWarning[] = []
|
|
747
952
|
if (region != null) {
|
|
748
953
|
region.warnings
|
|
749
954
|
.filter(
|
|
750
|
-
(warning) =>
|
|
955
|
+
(warning: { type: string; coverage: number }) =>
|
|
751
956
|
this.visibleWarnings.includes(warning.type) &&
|
|
752
|
-
warning.coverage >=
|
|
957
|
+
warning.coverage >= coverageCriterionVal
|
|
753
958
|
)
|
|
754
|
-
.forEach((warningByType) => {
|
|
959
|
+
.forEach((warningByType: { type: string; identifiers: string[] }) => {
|
|
755
960
|
warningByType.identifiers.forEach((identifier) => {
|
|
756
|
-
const warning = this.warnings[identifier]
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
961
|
+
const warning = this.warnings?.[identifier]
|
|
962
|
+
if (warning) {
|
|
963
|
+
popupWarningsData.push({
|
|
964
|
+
id: identifier,
|
|
965
|
+
type: warningByType.type,
|
|
966
|
+
severity: warning.severity,
|
|
967
|
+
direction: warning.direction,
|
|
968
|
+
text: warning.text != null ? warning.text : '',
|
|
969
|
+
interval: warning.validInterval,
|
|
970
|
+
})
|
|
971
|
+
}
|
|
764
972
|
})
|
|
765
973
|
})
|
|
766
974
|
}
|
|
767
|
-
if (
|
|
768
|
-
|
|
975
|
+
if (popupWarningsData.length === 0) {
|
|
976
|
+
popupWarningsData = [
|
|
769
977
|
{
|
|
978
|
+
id: 'no-warnings',
|
|
770
979
|
type: '',
|
|
771
980
|
severity: 0,
|
|
772
981
|
direction: 0,
|
|
@@ -781,53 +990,66 @@ export default {
|
|
|
781
990
|
severity = this.coverageRegions[regionId]
|
|
782
991
|
}
|
|
783
992
|
this.popupLevel = `level-${severity}`
|
|
784
|
-
this.popupWarnings =
|
|
785
|
-
const
|
|
993
|
+
this.popupWarnings = popupWarningsData
|
|
994
|
+
const dayMapLarge = this.$refs.dayMapLarge as HTMLDivElement | undefined
|
|
995
|
+
const mapRect = dayMapLarge?.getBoundingClientRect()
|
|
786
996
|
if (
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
window,
|
|
792
|
-
window.scrollX,
|
|
793
|
-
window.scrollY,
|
|
794
|
-
].every((item) => item != null)
|
|
997
|
+
mapRect &&
|
|
998
|
+
[mapRect.x, mapRect.y, window.scrollX, window.scrollY].every(
|
|
999
|
+
(item) => item != null
|
|
1000
|
+
)
|
|
795
1001
|
) {
|
|
796
1002
|
this.tooltipX = event.pageX - mapRect.x - window.scrollX
|
|
797
1003
|
this.tooltipY = event.pageY - mapRect.y - window.scrollY
|
|
798
1004
|
this.showTooltip = true
|
|
799
1005
|
}
|
|
800
1006
|
},
|
|
801
|
-
validIconLocation(coord, warningId) {
|
|
1007
|
+
validIconLocation(coord: [number, number], warningId: string): boolean {
|
|
802
1008
|
const warnings = this.warnings
|
|
803
|
-
const warning = warnings[warningId]
|
|
804
|
-
|
|
1009
|
+
const warning = warnings?.[warningId]
|
|
1010
|
+
if (!warning) return true
|
|
1011
|
+
|
|
1012
|
+
const geometriesData = this.geometries as Record<
|
|
1013
|
+
string,
|
|
1014
|
+
Record<string, RegionGeometry>
|
|
1015
|
+
>
|
|
1016
|
+
|
|
1017
|
+
const activeIconRegions: Record<string, boolean> = {}
|
|
805
1018
|
this.icons.forEach((icon) => {
|
|
806
|
-
|
|
1019
|
+
if (icon.regionId) {
|
|
1020
|
+
activeIconRegions[icon.regionId] = true
|
|
1021
|
+
}
|
|
807
1022
|
})
|
|
1023
|
+
const geomYear = geometriesData[this.geometryId]
|
|
808
1024
|
return ![...warning.covRegions.keys()].some((covRegion) => {
|
|
809
1025
|
if (!activeIconRegions[covRegion]) {
|
|
810
1026
|
return false
|
|
811
1027
|
}
|
|
812
|
-
const center =
|
|
1028
|
+
const center = geomYear?.[covRegion]?.center
|
|
1029
|
+
if (!center) return false
|
|
813
1030
|
return (
|
|
814
1031
|
(center[0] - coord[0]) ** 2 + (center[1] - coord[1]) ** 2 <
|
|
815
1032
|
this.minIconDistSqr
|
|
816
1033
|
)
|
|
817
1034
|
})
|
|
818
1035
|
},
|
|
819
|
-
mergeNetworks(networks) {
|
|
1036
|
+
mergeNetworks(networks: Set<string>[]): boolean {
|
|
1037
|
+
const geometriesData = this.geometries as Record<
|
|
1038
|
+
string,
|
|
1039
|
+
Record<string, RegionGeometry>
|
|
1040
|
+
>
|
|
1041
|
+
const geomYear = geometriesData[this.geometryId]
|
|
1042
|
+
|
|
820
1043
|
return networks.some((network1, index1) => {
|
|
821
1044
|
const neighbours = Array.from(network1.keys()).reduce(
|
|
822
|
-
(reduced
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
)
|
|
1045
|
+
(reduced: Set<string>, region: string) => {
|
|
1046
|
+
const regionGeom = geomYear?.[region]
|
|
1047
|
+
regionGeom?.neighbours?.forEach((neighbour: string) => {
|
|
1048
|
+
reduced.add(neighbour)
|
|
1049
|
+
})
|
|
828
1050
|
return reduced
|
|
829
1051
|
},
|
|
830
|
-
new Set()
|
|
1052
|
+
new Set<string>()
|
|
831
1053
|
)
|
|
832
1054
|
return networks.some((network2, index2) => {
|
|
833
1055
|
if (index2 <= index1) {
|
|
@@ -837,7 +1059,10 @@ export default {
|
|
|
837
1059
|
(neighbour) => network2.has(neighbour)
|
|
838
1060
|
)
|
|
839
1061
|
if (ngbrIndex >= 0) {
|
|
840
|
-
|
|
1062
|
+
const targetNetwork = networks[index1]
|
|
1063
|
+
if (targetNetwork) {
|
|
1064
|
+
network2.forEach(targetNetwork.add, targetNetwork)
|
|
1065
|
+
}
|
|
841
1066
|
networks.splice(index2, 1)
|
|
842
1067
|
return true
|
|
843
1068
|
}
|
|
@@ -845,68 +1070,79 @@ export default {
|
|
|
845
1070
|
})
|
|
846
1071
|
})
|
|
847
1072
|
},
|
|
848
|
-
indexOfSmallest(array) {
|
|
1073
|
+
indexOfSmallest(array: number[]): number {
|
|
849
1074
|
let lowest = 0
|
|
850
1075
|
for (let i = 1; i < array.length; i++) {
|
|
851
|
-
|
|
1076
|
+
const current = array[i]
|
|
1077
|
+
const lowestVal = array[lowest]
|
|
1078
|
+
if (
|
|
1079
|
+
current !== undefined &&
|
|
1080
|
+
lowestVal !== undefined &&
|
|
1081
|
+
current < lowestVal
|
|
1082
|
+
) {
|
|
1083
|
+
lowest = i
|
|
1084
|
+
}
|
|
852
1085
|
}
|
|
853
1086
|
return lowest
|
|
854
1087
|
},
|
|
855
|
-
zoomIn() {
|
|
1088
|
+
zoomIn(): void {
|
|
856
1089
|
if (this.panzoom != null) {
|
|
857
1090
|
this.panzoom.zoom(this.panzoom.getScale() + 1, {
|
|
858
1091
|
force: true,
|
|
859
1092
|
})
|
|
860
1093
|
}
|
|
861
1094
|
},
|
|
862
|
-
zoomOut() {
|
|
1095
|
+
zoomOut(): void {
|
|
863
1096
|
if (this.panzoom != null) {
|
|
864
1097
|
this.panzoom.zoom(this.panzoom.getScale() - 1, {
|
|
865
1098
|
force: true,
|
|
866
1099
|
})
|
|
867
1100
|
}
|
|
868
1101
|
},
|
|
869
|
-
closeTooltip(event) {
|
|
1102
|
+
closeTooltip(event: MouseEvent): void {
|
|
870
1103
|
event.preventDefault()
|
|
871
1104
|
this.showTooltip = false
|
|
872
1105
|
},
|
|
873
|
-
moveWest(event) {
|
|
1106
|
+
moveWest(event: KeyboardEvent): void {
|
|
874
1107
|
event.preventDefault()
|
|
875
|
-
this.panzoom
|
|
1108
|
+
this.panzoom?.pan(this.moveStep, 0, {
|
|
876
1109
|
relative: true,
|
|
877
1110
|
})
|
|
878
1111
|
this.limitPan()
|
|
879
1112
|
},
|
|
880
|
-
moveEast(event) {
|
|
1113
|
+
moveEast(event: KeyboardEvent): void {
|
|
881
1114
|
event.preventDefault()
|
|
882
|
-
this.panzoom
|
|
1115
|
+
this.panzoom?.pan(-this.moveStep, 0, {
|
|
883
1116
|
relative: true,
|
|
884
1117
|
})
|
|
885
1118
|
this.limitPan()
|
|
886
1119
|
},
|
|
887
|
-
moveNorth(event) {
|
|
1120
|
+
moveNorth(event: KeyboardEvent): void {
|
|
888
1121
|
event.preventDefault()
|
|
889
|
-
this.panzoom
|
|
1122
|
+
this.panzoom?.pan(0, this.moveStep, {
|
|
890
1123
|
relative: true,
|
|
891
1124
|
})
|
|
892
1125
|
this.limitPan()
|
|
893
1126
|
},
|
|
894
|
-
moveSouth(event) {
|
|
1127
|
+
moveSouth(event: KeyboardEvent): void {
|
|
895
1128
|
event.preventDefault()
|
|
896
|
-
this.panzoom
|
|
1129
|
+
this.panzoom?.pan(0, -this.moveStep, {
|
|
897
1130
|
relative: true,
|
|
898
1131
|
})
|
|
899
1132
|
this.limitPan()
|
|
900
1133
|
},
|
|
901
|
-
limitPan() {
|
|
1134
|
+
limitPan(): void {
|
|
1135
|
+
if (!this.panzoom) return
|
|
1136
|
+
|
|
902
1137
|
const pan = this.panzoom.getPan()
|
|
1138
|
+
const panLimitsVal = this.panLimits as { x: number; y: number }
|
|
903
1139
|
let panChanged = false
|
|
904
|
-
;['x', 'y'].forEach((coord) => {
|
|
905
|
-
if (pan[coord] >
|
|
906
|
-
pan[coord] =
|
|
1140
|
+
;(['x', 'y'] as const).forEach((coord) => {
|
|
1141
|
+
if (pan[coord] > panLimitsVal[coord]) {
|
|
1142
|
+
pan[coord] = panLimitsVal[coord]
|
|
907
1143
|
panChanged = true
|
|
908
|
-
} else if (pan[coord] < -
|
|
909
|
-
pan[coord] = -
|
|
1144
|
+
} else if (pan[coord] < -panLimitsVal[coord]) {
|
|
1145
|
+
pan[coord] = -panLimitsVal[coord]
|
|
910
1146
|
panChanged = true
|
|
911
1147
|
}
|
|
912
1148
|
})
|
|
@@ -914,20 +1150,21 @@ export default {
|
|
|
914
1150
|
this.panzoom.pan(pan.x, pan.y)
|
|
915
1151
|
}
|
|
916
1152
|
},
|
|
917
|
-
isAttached(node) {
|
|
918
|
-
let currentNode = node
|
|
1153
|
+
isAttached(node: Node | null): boolean {
|
|
1154
|
+
let currentNode: Node | null = node
|
|
919
1155
|
while (currentNode != null && currentNode.parentNode != null) {
|
|
920
1156
|
if (currentNode.parentNode === document) {
|
|
921
1157
|
return true
|
|
922
1158
|
}
|
|
923
|
-
currentNode =
|
|
924
|
-
|
|
925
|
-
|
|
1159
|
+
currentNode =
|
|
1160
|
+
currentNode.parentNode instanceof ShadowRoot
|
|
1161
|
+
? currentNode.parentNode.host
|
|
1162
|
+
: currentNode.parentNode
|
|
926
1163
|
}
|
|
927
1164
|
return false
|
|
928
|
-
}
|
|
1165
|
+
},
|
|
929
1166
|
},
|
|
930
|
-
}
|
|
1167
|
+
})
|
|
931
1168
|
</script>
|
|
932
1169
|
|
|
933
1170
|
<style lang="scss">
|
|
@@ -975,22 +1212,8 @@ button.fmi-warnings-map-tool {
|
|
|
975
1212
|
background-repeat: no-repeat;
|
|
976
1213
|
background-position: center;
|
|
977
1214
|
cursor: pointer;
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
.light-theme button.fmi-warnings-map-tool {
|
|
981
|
-
border-color: $light-button-border-color;
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
.dark-theme button.fmi-warnings-map-tool {
|
|
985
|
-
border-color: $dark-button-border-color;
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
.light-gray-theme button.fmi-warnings-map-tool {
|
|
989
|
-
border-color: $light-gray-button-border-color;
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
.dark-gray-theme button.fmi-warnings-map-tool {
|
|
993
|
-
border-color: $dark-gray-button-border-color;
|
|
1215
|
+
border: none;
|
|
1216
|
+
padding: 0;
|
|
994
1217
|
}
|
|
995
1218
|
|
|
996
1219
|
div.map-large div.day-map-large button {
|