-
Notifications
You must be signed in to change notification settings - Fork 25
/
Copy pathdecorate.mjs
310 lines (226 loc) · 7.88 KB
/
decorate.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
/**
### mapp.layer.decorate()
Decorates a layer object with additional properties and methods.
A mapview must be assigned to the layer object in order to decorate a layer.
The layer decorator passes the layer to a defined format method which assigns the Openlayers layer object (L).
Common interface methods such as layer.show, and hide are assigned to the layer object.
A blank layer.filter object will be set if the filter has not been defined in the JSON layer.
The first theme from the layer.style.themes array will be assigned as layer.style.theme if not already set.
Any plugins matching layer keys will be executed with the layer being passed as argument to the plugin method.
The layer object is returned from the decorator.
@module /layer/decorate
*/
/**
@function decorator
@async
@description
The layer decorator method create mapp-layer typedef object from a json-layer.
@param {object} layer JSON layer.
@returns {layer} Decorated Mapp Layer.
*/
export default async function decorate(layer) {
// Decorate layer format methods.
await mapp.layer.formats[layer.format]?.(layer);
// If layer does not exist, return.
if (!layer.L) return;
// The layer may be zoom level restricted.
if (layer.tables || layer.params?.viewport) {
layer.mapview.Map.getTargetElement()
.addEventListener('changeEnd', () => changeEnd(layer))
}
// Assign show, hide, and other methods to the layer object.
Object.assign(layer, {
show,
showCallbacks: [],
hide,
hideCallbacks: [],
tableCurrent,
geomCurrent,
zoomToExtent,
});
// Warn if outdated layer.edit configuration is used.
// Set layer.draw to layer.edit if it exists.
if (layer.edit) {
console.warn(`Layer: ${layer.key}, please update edit:{} to use draw:{} as layer.edit has been superseeded with layer.draw to be in line with the OL drawing interaction.`);
layer.draw = Object.assign(layer.draw || {}, layer.edit);
}
// Layer must have an empty draw config to allow for role-based assignment of drawing methods.
layer.draw ??= {};
// Warn if outdated layer.draw.delete configuration is used.
if (layer.draw?.delete) {
console.warn(`Layer: ${layer.key}, please move draw.delete to use layer.deleteLocation:true.`);
}
// Set layer filter.
layer.filter = {
current: {},
...layer.filter
}
// Set layer opacity from style.
layer.L.setOpacity(layer.style?.opacity || 1);
// Check which infoj entries should be skipped.
if (Array.isArray(layer.infoj_skip)) {
layer.infoj
.filter(entry => [
entry.key,
entry.field,
entry.query,
entry.type,
entry.group]
// Some object property value is included in infoj_skip array.
.some(val => layer.infoj_skip.includes(val)))
// Set skipEntry flag if some entry property is included in infoj_skip array.
.forEach(entry => entry.skipEntry = true)
}
// Call layer and/or plugin methods.
Object.keys(layer).forEach((key) => {
typeof mapp.layer[key] === 'function' && mapp.layer[key]?.(layer);
typeof mapp.plugins[key] === 'function' && mapp.plugins[key]?.(layer);
});
return layer;
}
/**
@function show
@description
Shows the layer on the map.
*/
function show() {
// Push the layer into the layers hook array.
this.mapview.hooks && mapp.hooks.push('layers', this.key);
this.display = true;
try {
// Add layer to map
this.mapview.Map.addLayer(this.L);
} catch {
// Will catch assertation error when layer is already added.
}
// Reload layer data if available.
this.reload instanceof Function && this.reload();
// Add layer attribution to mapview attribution.
this.mapview.attribution?.check();
// Execute showCallbacks
this.showCallbacks?.forEach(fn => fn instanceof Function && fn(this));
}
/**
@function hide
@description
Hides the layer from the map.
*/
function hide() {
this.display = false;
// Remove OL layer from mapview.
this.mapview.Map.removeLayer(this.L);
// Remove layer attribution from mapview attribution.
this.mapview.attribution?.check();
// Filter the layer from the layers hook array.
if (this.mapview.hooks && !this.zoomDisplay) {
mapp.hooks.filter('layers', this.key);
}
// Execute hideCallbacks
this.hideCallbacks?.forEach(fn => fn instanceof Function && fn(this));
}
/**
@function tableCurrent
@description
Returns the current table associated with the layer.
@returns {string} The current table associated with the layer.
*/
function tableCurrent() {
// Return the current table if it exists.
if (!this.tables) return this.table;
let table;
// Get current zoom level from mapview.
const zoom = parseInt(this.mapview.Map.getView().getZoom());
// Get zoom level keys from layer.tables object.
const zoomKeys = Object.keys(this.tables);
// Get first zoom level key from array.
const minZoomKey = parseInt(zoomKeys[0]);
const maxZoomKey = parseInt(zoomKeys[zoomKeys.length - 1]);
// Get the table for the current zoom level.
table = this.tables[zoom];
// Get the first table if the current zoom level is smaller than the min.
table = zoom < minZoomKey ? this.tables[minZoomKey] : table;
// Get the last table if the current zoom level is larger than the max.
table = zoom > maxZoomKey ? this.tables[maxZoomKey] : table;
return table;
}
/**
@function geomCurrent
@description
Returns the current geometry associated with the layer.
@returns {string} The current geometry associated with the layer.
*/
function geomCurrent() {
// Return the current geometry if it exists.
if (!this.geoms) return this.geom;
let geom;
// Get current zoom level from mapview.
const zoom = parseInt(this.mapview.Map.getView().getZoom());
// Get zoom level keys from layer.tables object.
const zoomKeys = Object.keys(this.geoms);
// Get first zoom level key from array.
const minZoomKey = parseInt(zoomKeys[0]);
const maxZoomKey = parseInt(zoomKeys[zoomKeys.length - 1]);
// Get the geometry for the current zoom level.
geom = this.geoms[zoom];
// Get the first geometry if the current zoom level is smaller than the min.
geom = zoom < minZoomKey ? this.geoms[minZoomKey] : geom;
// Get the last geometry if the current zoom level is larger than the max.
geom = zoom > maxZoomKey ? this.geoms[maxZoomKey] : geom;
return geom;
}
/**
@function zoomToExtent
@async
@description
Zooms to a specific extent on the map.
@param {Object} params - Parameters for zooming to extent.
@returns {Promise<boolean>} A promise that resolves with a boolean indicating the success of the operation.
*/
async function zoomToExtent(params) {
// Zooms to a specific extent.
// XMLHttpRequest to layer extent endpoint
const response = await mapp.utils.xhr(`${this.mapview.host}/api/query/layer_extent?` +
mapp.utils.paramString({ // build query string for the url
locale: this.mapview.locale.key,
layer: this.key,
table: this.table || Object.values(this.tables)[0] || Object.values(this.tables)[1],
geom: this.geom,
proj: this.srid,
srid: this.mapview.srid,
filter: this.filter.current
})
);
// If failed to fetch response
if (!response) {
return false;
}
// re matches text in parentheses
const bounds = /\((.*?)\)/.exec(response.box2d)[1].replace(/ /g, ',');
this.mapview.fitView( // fit the map view within bounds
bounds.split(',').map( // get a float array of bounds
coords => parseFloat(coords)
),
params
);
return true;
}
function changeEnd(layer) {
// Layer is out of zoom range.
if (!layer.tableCurrent()) {
if (layer.display) {
// Layer should be shown if possible.
layer.zoomDisplay = true
layer.hide()
}
return;
}
if (layer.zoomDisplay) {
// Prevents layer.show() being fired on zoom change within range.
delete layer.zoomDisplay
// Show layer if within zoomDisplay range.
layer.show()
}
if (layer.params.viewport) {
layer.reload()
}
}