@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
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internationalization composable
|
|
3
|
+
*
|
|
4
|
+
* Provides translation function for the alert client.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import DOMPurify from 'dompurify'
|
|
8
|
+
import { isRef, type MaybeRef } from 'vue'
|
|
9
|
+
import type { Language } from '@/types'
|
|
10
|
+
|
|
11
|
+
import en from '@/locales/en.json'
|
|
12
|
+
import fi from '@/locales/fi.json'
|
|
13
|
+
import sv from '@/locales/sv.json'
|
|
14
|
+
|
|
15
|
+
type TranslationKey = keyof typeof fi | keyof typeof en | keyof typeof sv
|
|
16
|
+
|
|
17
|
+
const translations: Record<Language, Record<string, string>> = {
|
|
18
|
+
en,
|
|
19
|
+
fi,
|
|
20
|
+
sv,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface UseI18nReturn {
|
|
24
|
+
t: (key: string | null | undefined) => string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* i18n composable for translations
|
|
29
|
+
*
|
|
30
|
+
* @param language - Language code, can be a ref or plain string
|
|
31
|
+
* @returns Object with translation function t()
|
|
32
|
+
*/
|
|
33
|
+
export function useI18n(
|
|
34
|
+
language: MaybeRef<Language | string | null | undefined>
|
|
35
|
+
): UseI18nReturn {
|
|
36
|
+
/**
|
|
37
|
+
* Translate a key to the current language
|
|
38
|
+
* Returns empty string if language or key is not set
|
|
39
|
+
* Sanitizes output with DOMPurify to prevent XSS
|
|
40
|
+
*/
|
|
41
|
+
function t(key: string | null | undefined): string {
|
|
42
|
+
const langValue = isRef(language) ? language.value : language
|
|
43
|
+
|
|
44
|
+
if (langValue == null || key == null) {
|
|
45
|
+
return ''
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const lang = langValue as Language
|
|
49
|
+
const translationMap = translations[lang]
|
|
50
|
+
|
|
51
|
+
if (!translationMap) {
|
|
52
|
+
return ''
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const translation = translationMap[key as TranslationKey]
|
|
56
|
+
return translation ? DOMPurify.sanitize(translation) : ''
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
t,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyboard navigation key codes composable
|
|
3
|
+
*
|
|
4
|
+
* Provides key code constants for keyboard navigation in the alert client.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { KEY_CODES } from '@/types'
|
|
8
|
+
|
|
9
|
+
export function useKeyCodes() {
|
|
10
|
+
return {
|
|
11
|
+
KEY_CODE_END: KEY_CODES.END,
|
|
12
|
+
KEY_CODE_HOME: KEY_CODES.HOME,
|
|
13
|
+
KEY_CODE_LEFT: KEY_CODES.LEFT,
|
|
14
|
+
KEY_CODE_RIGHT: KEY_CODES.RIGHT,
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map paths composable
|
|
3
|
+
*
|
|
4
|
+
* Provides computed path data for map visualization components.
|
|
5
|
+
* Extracts path-related logic from utils mixin.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { computed, ref, type Ref, type ComputedRef } from 'vue'
|
|
9
|
+
import type {
|
|
10
|
+
WarningsMap,
|
|
11
|
+
DayRegions,
|
|
12
|
+
Theme,
|
|
13
|
+
Severity,
|
|
14
|
+
GeometryCollection,
|
|
15
|
+
ThemeColorMap,
|
|
16
|
+
RegionGeometry,
|
|
17
|
+
} from '@/types'
|
|
18
|
+
import { isRegionGeometry } from '@/types'
|
|
19
|
+
import { useConfig, REGION_LAND, REGION_SEA, REGION_LAKE } from './useConfig'
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Types
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
export interface PathData {
|
|
26
|
+
key: string
|
|
27
|
+
fill: string
|
|
28
|
+
d: string
|
|
29
|
+
opacity: string
|
|
30
|
+
dataRegion?: string
|
|
31
|
+
dataSeverity?: Severity
|
|
32
|
+
strokeWidth: number | string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface BorderPath {
|
|
36
|
+
key: string
|
|
37
|
+
d: string
|
|
38
|
+
opacity: string
|
|
39
|
+
strokeWidth: number | string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface CoverageData {
|
|
43
|
+
key: string
|
|
44
|
+
d: string
|
|
45
|
+
fillOpacity: number
|
|
46
|
+
strokeWidth: number | string
|
|
47
|
+
fill: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface PathOptions {
|
|
51
|
+
type: string
|
|
52
|
+
severity?: Severity | null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface RegionVisualization {
|
|
56
|
+
geom: RegionGeometry
|
|
57
|
+
severity: Severity
|
|
58
|
+
color: string
|
|
59
|
+
visible: boolean
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface RegionWarningItem {
|
|
63
|
+
key: string
|
|
64
|
+
warnings: Array<{
|
|
65
|
+
type: string
|
|
66
|
+
coverage: number
|
|
67
|
+
identifiers: string[]
|
|
68
|
+
}>
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// Composable Options
|
|
73
|
+
// ============================================================================
|
|
74
|
+
|
|
75
|
+
export interface UseMapPathsOptions {
|
|
76
|
+
/** Map size: 'Large' or 'Small' */
|
|
77
|
+
size: Ref<'Large' | 'Small'>
|
|
78
|
+
/** Day index (0-4) */
|
|
79
|
+
index: Ref<number>
|
|
80
|
+
/** Day regions data */
|
|
81
|
+
input: Ref<DayRegions>
|
|
82
|
+
/** All warnings map */
|
|
83
|
+
warnings: Ref<WarningsMap | null>
|
|
84
|
+
/** Currently visible warning types */
|
|
85
|
+
visibleWarnings: Ref<string[]>
|
|
86
|
+
/** Geometry ID */
|
|
87
|
+
geometryId: Ref<number>
|
|
88
|
+
/** Current theme */
|
|
89
|
+
theme: Ref<Theme | string>
|
|
90
|
+
/** Loading state */
|
|
91
|
+
loading: Ref<boolean>
|
|
92
|
+
/** Stroke width for paths */
|
|
93
|
+
strokeWidth: Ref<number | string>
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface UseMapPathsReturn {
|
|
97
|
+
strokeColor: ComputedRef<string>
|
|
98
|
+
bluePaths: ComputedRef<PathData[]>
|
|
99
|
+
greenPaths: ComputedRef<PathData[]>
|
|
100
|
+
yellowPaths: ComputedRef<PathData[]>
|
|
101
|
+
orangePaths: ComputedRef<PathData[]>
|
|
102
|
+
redPaths: ComputedRef<PathData[]>
|
|
103
|
+
overlayPaths: ComputedRef<BorderPath[]>
|
|
104
|
+
landBorders: ComputedRef<BorderPath[]>
|
|
105
|
+
seaBorders: ComputedRef<BorderPath[]>
|
|
106
|
+
yellowCoverages: ComputedRef<CoverageData[]>
|
|
107
|
+
orangeCoverages: ComputedRef<CoverageData[]>
|
|
108
|
+
redCoverages: ComputedRef<CoverageData[]>
|
|
109
|
+
overlayCoverages: ComputedRef<CoverageData[]>
|
|
110
|
+
coverageRegions: Ref<Record<string, number>>
|
|
111
|
+
coverageWarnings: Ref<string[]>
|
|
112
|
+
regionData: (regionId: string) => RegionWarningItem | null
|
|
113
|
+
regionVisualization: (regionId: string) => RegionVisualization
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ============================================================================
|
|
117
|
+
// Composable
|
|
118
|
+
// ============================================================================
|
|
119
|
+
|
|
120
|
+
export function useMapPaths(options: UseMapPathsOptions): UseMapPathsReturn {
|
|
121
|
+
const {
|
|
122
|
+
size,
|
|
123
|
+
index,
|
|
124
|
+
input,
|
|
125
|
+
warnings,
|
|
126
|
+
visibleWarnings,
|
|
127
|
+
geometryId,
|
|
128
|
+
theme,
|
|
129
|
+
loading,
|
|
130
|
+
strokeWidth,
|
|
131
|
+
} = options
|
|
132
|
+
|
|
133
|
+
// Get config data
|
|
134
|
+
const config = useConfig()
|
|
135
|
+
const { geometries, colors, regionIds, coverageCriterion } = config
|
|
136
|
+
|
|
137
|
+
// Local state for coverage tracking
|
|
138
|
+
const coverageRegions = ref<Record<string, number>>({})
|
|
139
|
+
const coverageWarnings = ref<string[]>([])
|
|
140
|
+
|
|
141
|
+
// ============================================================================
|
|
142
|
+
// Helper Functions
|
|
143
|
+
// ============================================================================
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get region warning data for a specific region
|
|
147
|
+
*/
|
|
148
|
+
function regionData(regionId: string): RegionWarningItem | null {
|
|
149
|
+
const geomData = geometries as GeometryCollection
|
|
150
|
+
const region = geomData?.[geometryId.value]?.[regionId]
|
|
151
|
+
if (!region || !isRegionGeometry(region)) return null
|
|
152
|
+
|
|
153
|
+
const regionType = region.type as 'land' | 'sea'
|
|
154
|
+
const dayInput = input.value
|
|
155
|
+
if (!dayInput) return null
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
(dayInput[regionType] as RegionWarningItem[] | undefined)?.find(
|
|
159
|
+
(data: RegionWarningItem) => data.key === regionId
|
|
160
|
+
) ?? null
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get severity for a region based on warnings
|
|
166
|
+
*/
|
|
167
|
+
function regionSeverity(regionId: string): Severity {
|
|
168
|
+
const geomData = geometries as GeometryCollection
|
|
169
|
+
const region = regionData(regionId)
|
|
170
|
+
let severity: Severity = 0
|
|
171
|
+
|
|
172
|
+
if (region != null && warnings.value) {
|
|
173
|
+
const warningsMap = warnings.value
|
|
174
|
+
region.warnings.find((warning) => {
|
|
175
|
+
if (visibleWarnings.value.includes(warning.type)) {
|
|
176
|
+
const topIdentifier = warning.identifiers.find(
|
|
177
|
+
(id) => warningsMap[id] && warningsMap[id].covRegions.size === 0
|
|
178
|
+
)
|
|
179
|
+
if (topIdentifier != null && warningsMap[topIdentifier]) {
|
|
180
|
+
severity = warningsMap[topIdentifier].severity
|
|
181
|
+
return true
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return false
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const geomEntry = geomData[geometryId.value]?.[regionId]
|
|
189
|
+
const parentId = isRegionGeometry(geomEntry) ? geomEntry.parent : undefined
|
|
190
|
+
if (parentId) {
|
|
191
|
+
severity = Math.max(severity, regionSeverity(parentId)) as Severity
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return severity
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Get visualization data for a region
|
|
199
|
+
*/
|
|
200
|
+
function regionVisualization(regionId: string): RegionVisualization {
|
|
201
|
+
const geomData = geometries as GeometryCollection
|
|
202
|
+
const geomEntry = geomData[geometryId.value]?.[regionId]
|
|
203
|
+
const severity = regionSeverity(regionId)
|
|
204
|
+
|
|
205
|
+
// Return default visualization if not a valid region geometry
|
|
206
|
+
if (!isRegionGeometry(geomEntry)) {
|
|
207
|
+
const themeColors = colors?.[theme.value as keyof ThemeColorMap]
|
|
208
|
+
return {
|
|
209
|
+
geom: {
|
|
210
|
+
name: regionId,
|
|
211
|
+
type: 'land',
|
|
212
|
+
parent: '',
|
|
213
|
+
children: [],
|
|
214
|
+
neighbours: [],
|
|
215
|
+
weight: 0,
|
|
216
|
+
center: [0, 0],
|
|
217
|
+
},
|
|
218
|
+
severity,
|
|
219
|
+
color: themeColors?.missing || '#cccccc',
|
|
220
|
+
visible: false,
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const geom = geomEntry
|
|
225
|
+
const isLand = geom.type === REGION_LAND
|
|
226
|
+
const themeColors = colors?.[theme.value as keyof ThemeColorMap]
|
|
227
|
+
const color =
|
|
228
|
+
severity || isLand
|
|
229
|
+
? themeColors?.levels?.[severity] || '#cccccc'
|
|
230
|
+
: themeColors?.sea || '#add8e6'
|
|
231
|
+
const visible = severity > 0 || geom.subType !== REGION_LAKE
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
geom,
|
|
235
|
+
severity,
|
|
236
|
+
color,
|
|
237
|
+
visible,
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Get area borders (land or sea)
|
|
243
|
+
*/
|
|
244
|
+
function areaBorders(area: 'land' | 'sea'): BorderPath[] {
|
|
245
|
+
const geomData = geometries as GeometryCollection
|
|
246
|
+
const pathKey = `path${size.value}` as 'pathLarge' | 'pathSmall'
|
|
247
|
+
|
|
248
|
+
return [
|
|
249
|
+
{
|
|
250
|
+
key: `border.${area}`,
|
|
251
|
+
d: geomData?.[geometryId.value]?.borders?.[area]?.[pathKey] || '',
|
|
252
|
+
opacity: '1',
|
|
253
|
+
strokeWidth: strokeWidth.value,
|
|
254
|
+
},
|
|
255
|
+
]
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Generate paths for a given region type and optional severity
|
|
260
|
+
*/
|
|
261
|
+
function paths(pathOptions: PathOptions): PathData[] {
|
|
262
|
+
const geomData = geometries as GeometryCollection
|
|
263
|
+
const themeColors = colors?.[theme.value as keyof ThemeColorMap]
|
|
264
|
+
const pathKey = `path${size.value}` as 'pathLarge' | 'pathSmall'
|
|
265
|
+
|
|
266
|
+
return regionIds.reduce((regions: PathData[], regionId: string) => {
|
|
267
|
+
const regionEntry = geomData?.[geometryId.value]?.[regionId]
|
|
268
|
+
if (!isRegionGeometry(regionEntry)) return regions
|
|
269
|
+
|
|
270
|
+
const region = regionEntry
|
|
271
|
+
if (
|
|
272
|
+
region[pathKey] &&
|
|
273
|
+
(region.type === pathOptions.type) === (region.subType == null)
|
|
274
|
+
) {
|
|
275
|
+
const visualization = regionVisualization(regionId)
|
|
276
|
+
if (
|
|
277
|
+
pathOptions.severity == null ||
|
|
278
|
+
visualization.severity === pathOptions.severity
|
|
279
|
+
) {
|
|
280
|
+
regions.push({
|
|
281
|
+
key: `${regionId}${size.value}${index.value}Path`,
|
|
282
|
+
fill: loading.value
|
|
283
|
+
? themeColors?.missing || '#cccccc'
|
|
284
|
+
: visualization.color,
|
|
285
|
+
d: visualization.visible
|
|
286
|
+
? (visualization.geom[pathKey] as string) || ''
|
|
287
|
+
: '',
|
|
288
|
+
opacity: '1',
|
|
289
|
+
dataRegion: regionId,
|
|
290
|
+
dataSeverity: visualization.severity,
|
|
291
|
+
strokeWidth:
|
|
292
|
+
region.type === 'sea' && region.subType !== 'lake'
|
|
293
|
+
? strokeWidth.value
|
|
294
|
+
: 0,
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return regions
|
|
299
|
+
}, [])
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Generate coverage geometry data
|
|
304
|
+
*/
|
|
305
|
+
function coverageGeom(
|
|
306
|
+
coverageProperty: 'coveragesLarge' | 'coveragesSmall',
|
|
307
|
+
covStrokeWidth: number,
|
|
308
|
+
fillOpacity: number,
|
|
309
|
+
severity?: Severity
|
|
310
|
+
): CoverageData[] {
|
|
311
|
+
const coverageData: CoverageData[] = []
|
|
312
|
+
const warningsMap = warnings.value
|
|
313
|
+
const themeColors = colors?.[theme.value as keyof ThemeColorMap]
|
|
314
|
+
|
|
315
|
+
if (!warningsMap) return coverageData
|
|
316
|
+
|
|
317
|
+
Object.keys(warningsMap).forEach((key) => {
|
|
318
|
+
const warning = warningsMap[key]
|
|
319
|
+
if (!warning) return
|
|
320
|
+
|
|
321
|
+
if (
|
|
322
|
+
(severity == null || warning.severity === severity) &&
|
|
323
|
+
warning.effectiveDays[index.value] &&
|
|
324
|
+
visibleWarnings.value.includes(warning.type) &&
|
|
325
|
+
warning.coveragesLarge.length > 0
|
|
326
|
+
) {
|
|
327
|
+
if (!coverageWarnings.value.includes(key)) {
|
|
328
|
+
;[...warning.covRegions.keys()].forEach((covRegion) => {
|
|
329
|
+
const covValue = warning.covRegions.get(covRegion)
|
|
330
|
+
if (
|
|
331
|
+
(coverageRegions.value[covRegion] == null ||
|
|
332
|
+
coverageRegions.value[covRegion] < warning.severity) &&
|
|
333
|
+
covValue != null &&
|
|
334
|
+
covValue >= coverageCriterion
|
|
335
|
+
) {
|
|
336
|
+
coverageRegions.value[covRegion] = warning.severity
|
|
337
|
+
}
|
|
338
|
+
})
|
|
339
|
+
coverageWarnings.value.push(key)
|
|
340
|
+
}
|
|
341
|
+
warning[coverageProperty].forEach(
|
|
342
|
+
(coverage: { path: string; reference: number[] }) => {
|
|
343
|
+
coverageData.push({
|
|
344
|
+
key: `${key}${size.value}${index.value}${fillOpacity}Coverage`,
|
|
345
|
+
d: coverage.path,
|
|
346
|
+
fillOpacity,
|
|
347
|
+
strokeWidth: covStrokeWidth,
|
|
348
|
+
fill: themeColors?.levels?.[warning.severity] || '#cccccc',
|
|
349
|
+
})
|
|
350
|
+
}
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
return coverageData
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ============================================================================
|
|
359
|
+
// Computed Properties
|
|
360
|
+
// ============================================================================
|
|
361
|
+
|
|
362
|
+
const strokeColor = computed<string>(() => {
|
|
363
|
+
return (
|
|
364
|
+
colors?.[theme.value as keyof ThemeColorMap]?.stroke ?? 'DarkSlateGray'
|
|
365
|
+
)
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
const bluePaths = computed<PathData[]>(() => {
|
|
369
|
+
return paths({ type: REGION_SEA })
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
const greenPaths = computed<PathData[]>(() => {
|
|
373
|
+
return paths({ type: REGION_LAND, severity: 0 })
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
const yellowPaths = computed<PathData[]>(() => {
|
|
377
|
+
return paths({ type: REGION_LAND, severity: 2 })
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
const orangePaths = computed<PathData[]>(() => {
|
|
381
|
+
return paths({ type: REGION_LAND, severity: 3 })
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
const redPaths = computed<PathData[]>(() => {
|
|
385
|
+
return paths({ type: REGION_LAND, severity: 4 })
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
const overlayPaths = computed<BorderPath[]>(() => {
|
|
389
|
+
const geomData = geometries as GeometryCollection
|
|
390
|
+
const pathKey = `path${size.value}` as 'pathLarge' | 'pathSmall'
|
|
391
|
+
|
|
392
|
+
return regionIds.reduce((regions: BorderPath[], regionId: string) => {
|
|
393
|
+
const regionEntry = geomData?.[geometryId.value]?.[regionId]
|
|
394
|
+
if (!isRegionGeometry(regionEntry)) return regions
|
|
395
|
+
|
|
396
|
+
const region = regionEntry
|
|
397
|
+
if (
|
|
398
|
+
region[pathKey] &&
|
|
399
|
+
(region.type === 'land' || region.subType === 'lake')
|
|
400
|
+
) {
|
|
401
|
+
const visualization = regionVisualization(regionId)
|
|
402
|
+
regions.push({
|
|
403
|
+
key: `${regionId}${size.value}${index.value}Overlay`,
|
|
404
|
+
d: visualization.visible
|
|
405
|
+
? (visualization.geom[pathKey] as string) || ''
|
|
406
|
+
: '',
|
|
407
|
+
opacity: '1',
|
|
408
|
+
strokeWidth: strokeWidth.value,
|
|
409
|
+
})
|
|
410
|
+
}
|
|
411
|
+
return regions
|
|
412
|
+
}, [])
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
const landBorders = computed<BorderPath[]>(() => {
|
|
416
|
+
return areaBorders('land')
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
const seaBorders = computed<BorderPath[]>(() => {
|
|
420
|
+
return areaBorders('sea')
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
const yellowCoverages = computed<CoverageData[]>(() => {
|
|
424
|
+
return coverageGeom(
|
|
425
|
+
`coverages${size.value}` as 'coveragesLarge' | 'coveragesSmall',
|
|
426
|
+
0,
|
|
427
|
+
1,
|
|
428
|
+
2
|
|
429
|
+
)
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
const orangeCoverages = computed<CoverageData[]>(() => {
|
|
433
|
+
return coverageGeom(
|
|
434
|
+
`coverages${size.value}` as 'coveragesLarge' | 'coveragesSmall',
|
|
435
|
+
0,
|
|
436
|
+
1,
|
|
437
|
+
3
|
|
438
|
+
)
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
const redCoverages = computed<CoverageData[]>(() => {
|
|
442
|
+
return coverageGeom(
|
|
443
|
+
`coverages${size.value}` as 'coveragesLarge' | 'coveragesSmall',
|
|
444
|
+
0,
|
|
445
|
+
1,
|
|
446
|
+
4
|
|
447
|
+
)
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
const overlayCoverages = computed<CoverageData[]>(() => {
|
|
451
|
+
return coverageGeom(
|
|
452
|
+
`coverages${size.value}` as 'coveragesLarge' | 'coveragesSmall',
|
|
453
|
+
1.1 * Number(strokeWidth.value),
|
|
454
|
+
0
|
|
455
|
+
)
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
return {
|
|
459
|
+
strokeColor,
|
|
460
|
+
bluePaths,
|
|
461
|
+
greenPaths,
|
|
462
|
+
yellowPaths,
|
|
463
|
+
orangePaths,
|
|
464
|
+
redPaths,
|
|
465
|
+
overlayPaths,
|
|
466
|
+
landBorders,
|
|
467
|
+
seaBorders,
|
|
468
|
+
yellowCoverages,
|
|
469
|
+
orangeCoverages,
|
|
470
|
+
redCoverages,
|
|
471
|
+
overlayCoverages,
|
|
472
|
+
coverageRegions,
|
|
473
|
+
coverageWarnings,
|
|
474
|
+
regionData,
|
|
475
|
+
regionVisualization,
|
|
476
|
+
}
|
|
477
|
+
}
|