Projekt

Obecné

Profil

Stáhnout (10.4 KB) Statistiky
| Větev: | Tag: | Revize:
1

    
2
import L, { LatLngTuple, PointExpression } from 'leaflet'
3
import { CatalogItemDto, ExternalCatalogItemDto, PathDto } from '../../swagger/data-contracts'
4
import generateUuid from '../../utils/id/uuidGenerator'
5

    
6
// Type of map point for conditional rendering
7
export enum MapPointType {
8
    ProcessedText, // From processed text
9
    LocalCatalog, // Fetched from local catalog
10
    ExternalCatalog, // Fetched from external catalog
11
    FileImport, // From GeoJSON file
12
    FromCoordinates, // From coordinates
13
}
14

    
15

    
16
// Represents a point on the map - wrapper for CatalogItemDto to make it easier to work with
17
export interface MapPoint {
18
    id: string // unique id to identify the point on the map
19
    reactId: string // unique id to identify item in React - e.g. rendering in a list
20
    idx: number // index in the path
21
    variantIdx: number, // index of the variant
22
    variants?: CatalogItemDto[], // variants of the point
23
    firstVariantCatalogItemId?: string // this id is only used for catalog items with multiple variants
24
    addToPath: boolean // whether to add the point to the path
25
    catalogItem: CatalogItemDto, // reference to CatalogItemDto
26
    type: MapPointType // Type of the map point
27
    hidden?: boolean // if true the point will not be displayed on the map
28
    externalSource?: string // if the point is from external source, this is the source name
29
}
30

    
31
export interface ExternalMapPoint extends MapPoint {
32
    catalogItem: ExternalCatalogItemDto
33
}
34

    
35
export type Path = MapPoint[]
36

    
37
export interface ExternalPath {
38
    idx: number,
39
    color: string,
40
    path: Path
41
    visible: boolean
42
    filename: string
43
}
44

    
45
export const getExternalPathColor = (idx: number) => {
46
    return `hsl(${idx * 360 / 10}, 100%, 50%)`
47
}
48

    
49
/**
50
 * Returns true whether the map point is displayable - i.e. can be shown on the map
51
 * @param mapPoint 
52
 * @returns true if the map point is displayable
53
 */
54
export const isMapPointDisplayable = (mapPoint: MapPoint): boolean =>
55
    !!mapPoint.catalogItem.latitude && !!mapPoint.catalogItem.longitude && !mapPoint.hidden
56

    
57
/**
58
 * Based on its type - either imported from local catalog, remote catalogs etc. each type has its own color to differentiate them
59
 * @param item item to get color for
60
 * @returns CSS color string
61
 */
62
export const getMapPointSemanticColor = (item: MapPoint) => {
63
    switch (item.type) {
64
        case MapPointType.LocalCatalog:
65
            return 'inherit'
66
        case MapPointType.FromCoordinates:
67
            return '#21972D'
68
        case MapPointType.ExternalCatalog:
69
            return '#A72020'
70
        case MapPointType.FileImport:
71
            return '#967520'
72
    }
73
}
74

    
75
/**
76
 * Creates SVG icon for map marker with specific color
77
 */
78
const createMapMarkerSvg = (color: string) => {
79
    return `data:image/svg+xml;utf8, ${encodeURIComponent(`
80
    <svg width="16px" height="16px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" version="1.1" fill="blue"
81
    stroke="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1">
82
    <path
83
        fill="${color}"
84
        d="m13.25 7c0 3.75-5.25 7.25-5.25 7.25s-5.25-3.5-5.25-7.25c0-2.89949 2.35051-5.25 5.25-5.25 2.8995 0 5.25 2.35051 5.25 5.25z" />
85
    <circle cx="8" cy="7" r="1.55" fill="white" />
86
    </svg> `)}`
87
}
88

    
89
const mapMarkerSvgs = {
90
    [MapPointType.ProcessedText]: createMapMarkerSvg('#285CAB'),
91
    [MapPointType.LocalCatalog]: createMapMarkerSvg('#00B0FF'),
92
    [MapPointType.ExternalCatalog]: createMapMarkerSvg('#A72020'),
93
    [MapPointType.FileImport]: createMapMarkerSvg('#967520'),
94
    [MapPointType.FromCoordinates]: createMapMarkerSvg('#21972D'),
95
}
96

    
97
const iconAnchor = [22, 22] as PointExpression
98
const iconSize = [35, 35] as PointExpression
99

    
100
const mapMarkers: any = {
101
    [MapPointType.ProcessedText]: L.icon({
102
        iconAnchor, iconSize,
103
        iconUrl: mapMarkerSvgs[MapPointType.ProcessedText],
104
    }),
105
    [MapPointType.LocalCatalog]: L.icon({
106
        iconAnchor, iconSize,
107
        iconUrl: mapMarkerSvgs[MapPointType.LocalCatalog],
108
    }),
109
    [MapPointType.ExternalCatalog]: L.icon({
110
        iconAnchor, iconSize,
111
        iconUrl: mapMarkerSvgs[MapPointType.ExternalCatalog],
112
    }),
113

    
114
    [MapPointType.FileImport]: L.icon({
115
        iconAnchor, iconSize,
116
        iconUrl: mapMarkerSvgs[MapPointType.FileImport],
117
    }),
118
    [MapPointType.FromCoordinates]: L.icon({
119
        iconAnchor, iconSize,
120
        iconUrl: mapMarkerSvgs[MapPointType.FromCoordinates],
121
    }),
122
}
123

    
124
export const getMapPointIcon = (item: MapPoint): L.Icon => mapMarkers[item.type]
125
export const getCustomMapPointIcon = (color: string) => {
126
    if (mapMarkers.hasOwnProperty(color)) {
127
        return mapMarkers[color] as L.Icon
128
    }
129

    
130
    const svg = createMapMarkerSvg(color)
131
    mapMarkers[color] = L.icon({
132
        iconAnchor, iconSize,
133
        iconUrl: svg,
134
    })
135

    
136
    return mapMarkers[color] as L.Icon
137
}
138

    
139
export const calculateMapCenter = (path: Path): LatLngTuple | undefined => {
140
    const displayableItems = path.filter((item) => isMapPointDisplayable(item))
141
    if (displayableItems.length === 0) {
142
        return undefined
143
    }
144

    
145
    return [
146
        displayableItems
147
            .map((item) => item.catalogItem.latitude ?? 0)
148
            .reduce((a, b) => a + b, 0) / displayableItems.length,
149
        displayableItems
150
            .map((item) => item.catalogItem.longitude ?? 0)
151
            .reduce((a, b) => a + b, 0) / displayableItems.length,
152
    ]
153
}
154

    
155
export const setMapPointIds = (pathVariant: Path): Path => pathVariant.map(mapPoint => {
156
    // Get identifier - either catalog item identifier is used or generate a new UUID if the point does not have uuid
157
    // - e.g. is not from the catalog
158
    return { ...mapPoint, id: mapPoint.catalogItem.id ? mapPoint.catalogItem.id : generateUuid() }
159
})
160

    
161
/**
162
 * Maps external catalog item to map point object
163
 * @param externalCatalogItem external catalog item to map to map point
164
 * @returns 
165
 */
166
export const mapExternalCatalogItemToMapPoint = (externalCatalogItem: ExternalCatalogItemDto) => {
167
    const coordinatesNotNull = externalCatalogItem.latitude !== null && externalCatalogItem.longitude !== null
168
    return ({
169
        id: externalCatalogItem.id,
170
        reactId: generateUuid(),
171
        idx: -1,
172
        addToPath: coordinatesNotNull,
173
        catalogItem: externalCatalogItem,
174
        type: MapPointType.ExternalCatalog,
175
        hidden: !coordinatesNotNull,
176
        externalSource: externalCatalogItem.externalSource,
177
    } as MapPoint)
178
}
179

    
180
/**
181
 * Maps local catalog item to map point
182
 * @param catalogItem catalog item to map to map point
183
 * @returns 
184
 */
185
export const mapLocalCatalogItemToMapPoint = (catalogItem: CatalogItemDto): ExternalMapPoint => {
186
    const coordinatesNotNull = catalogItem.latitude !== null && catalogItem.longitude !== null
187
    return ({
188
        id: catalogItem.id as string,
189
        reactId: generateUuid(),
190
        idx: -1,
191
        addToPath: coordinatesNotNull,
192
        catalogItem,
193
        type: MapPointType.LocalCatalog,
194
        hidden: !coordinatesNotNull,
195
        variantIdx: 0,
196
        variants: undefined
197
    })
198
}
199

    
200
/**
201
 * Builds path from pathDto
202
 * @param pathDto 
203
 * @returns 
204
 */
205
export const buildPath = (pathDto: PathDto): Path => {
206
    // Path dto contains an array of all variants of the catalog item in given path
207
    // By default we use the first variant of the catalog item, map the catalog item to MapPoint and add all the variants
208
    // to the map point + set the index to 0
209
    const path: Path = []
210
    pathDto.foundCatalogItems?.forEach(catalogItemVariants => {
211
        if (catalogItemVariants.length === 0) {
212
            return // This should never happen but should backend fail we will ignore empty arrays
213
        }
214

    
215
        const catalogItem = catalogItemVariants[0]
216
        const coordinatesNotNull = catalogItem.latitude !== null && catalogItem.longitude !== null // backend sends null for unknown coords
217
        path.push({
218
            id: catalogItem.id, // id should be the same as catalog item's id so that we do not render the same point multiple times
219
            reactId: generateUuid(), // react id should always be unique so that we can move the point in the list
220
            idx: path.length, // index in the result array
221
            variantIdx: 0, // we always pick the first variant
222
            addToPath: coordinatesNotNull,
223
            catalogItem,
224
            type: MapPointType.ProcessedText,
225
            hidden: !coordinatesNotNull,
226
            variants: catalogItemVariants.length === 1 ? undefined : catalogItemVariants,
227
            firstVariantCatalogItemId: catalogItem.id,
228
        } as MapPoint)
229
    })
230

    
231
    return path
232
}
233

    
234
/**
235
 * Updates map point if it has catalog item set or is in its variants
236
 */
237
export const updateMapPointIfCatalogItemPresent = (mapPoint: MapPoint, catalogItem: CatalogItemDto): MapPoint => {
238
    if (mapPoint.id !== catalogItem.id) {
239
        // If the id does not match catalog item id we need to check if the catalog item is in the variants
240
        if (!mapPoint.variants) {
241
            return mapPoint // if there are no variants we do not need to update the map point
242
        }
243

    
244
        const variantIdx = mapPoint.variants.findIndex(variant => variant.id === catalogItem.id)
245
        if (variantIdx === -1) {
246
            return mapPoint // if the catalog item is not in the variants we do not need to update the map point
247
        }
248

    
249
        // If the catalog item is in the variants we need to update the map point
250
        return {
251
            ...mapPoint,
252
            variants: mapPoint.variants.map((variant, idx) => idx === variantIdx ? catalogItem : variant),
253
        }
254
    }
255

    
256
    // Otherwise we have found map point that has same id as our catalog item
257
    // which means it will always have it in its variants if there are some
258
    const coordinatesNotNull = catalogItem.latitude !== null && catalogItem.longitude !== null
259
    if (!mapPoint.variants) {
260
        // If there are no variants simply update the map point
261
        return {
262
            ...mapPoint,
263
            catalogItem,
264
            addToPath: mapPoint.addToPath && coordinatesNotNull,
265
            hidden: mapPoint.hidden && !coordinatesNotNull,
266
        }
267
    }
268

    
269
    // If there are variants we need to update the map point in the variants as well
270
    const variantIdx = mapPoint.variants.findIndex(variant => variant.id === catalogItem.id)
271
    return {
272
        ...mapPoint,
273
        catalogItem,
274
        variants: mapPoint.variants.map((variant, idx) => idx === variantIdx ? catalogItem : variant),
275
        addToPath: mapPoint.addToPath && coordinatesNotNull,
276
        hidden: mapPoint.hidden && !coordinatesNotNull,
277
    }
278
}
(4-4/4)