@eeacms/volto-arcgis-block 0.1.31 → 0.1.34
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/CHANGELOG.md
CHANGED
|
@@ -4,8 +4,36 @@ All notable changes to this project will be documented in this file. Dates are d
|
|
|
4
4
|
|
|
5
5
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
6
6
|
|
|
7
|
+
#### [0.1.34](https://github.com/eea/volto-arcgis-block/compare/0.1.33...0.1.34)
|
|
8
|
+
|
|
9
|
+
- Bugs n improvements [`#105`](https://github.com/eea/volto-arcgis-block/pull/105)
|
|
10
|
+
- ESLint fix [`ac207b7`](https://github.com/eea/volto-arcgis-block/commit/ac207b727d43bc669b93ee273ca94e593f5fb190)
|
|
11
|
+
- ESLint fix [`96d2405`](https://github.com/eea/volto-arcgis-block/commit/96d240507f4e39e93d5d9bb43e8c66bc3962b3c3)
|
|
12
|
+
- Info widget [`4876e2b`](https://github.com/eea/volto-arcgis-block/commit/4876e2b13b81281202fcfd0d7f84eb03a7a96c8f)
|
|
13
|
+
- Promises.allSettled added [`95cbfc2`](https://github.com/eea/volto-arcgis-block/commit/95cbfc20b6117aeaa277378d56493dc9dcfbfd1f)
|
|
14
|
+
|
|
15
|
+
#### [0.1.33](https://github.com/eea/volto-arcgis-block/compare/0.1.32...0.1.33)
|
|
16
|
+
|
|
17
|
+
> 1 March 2022
|
|
18
|
+
|
|
19
|
+
- Develop [`#104`](https://github.com/eea/volto-arcgis-block/pull/104)
|
|
20
|
+
- Button fix [`#103`](https://github.com/eea/volto-arcgis-block/pull/103)
|
|
21
|
+
|
|
22
|
+
#### [0.1.32](https://github.com/eea/volto-arcgis-block/compare/0.1.31...0.1.32)
|
|
23
|
+
|
|
24
|
+
> 28 February 2022
|
|
25
|
+
|
|
26
|
+
- Develop [`#102`](https://github.com/eea/volto-arcgis-block/pull/102)
|
|
27
|
+
- Metadata [`#101`](https://github.com/eea/volto-arcgis-block/pull/101)
|
|
28
|
+
- linting fix [`a3f1b7d`](https://github.com/eea/volto-arcgis-block/commit/a3f1b7d29e7cfa91b5d48c5e6eaed9e79a707e14)
|
|
29
|
+
- Metadata info button implemented [`06659e0`](https://github.com/eea/volto-arcgis-block/commit/06659e090051659a28c3f8026a606d9e473e9ebd)
|
|
30
|
+
- Metadata Info icon implementation [`4750835`](https://github.com/eea/volto-arcgis-block/commit/475083515975126e0986a65eb2058552b2ba05bd)
|
|
31
|
+
|
|
7
32
|
#### [0.1.31](https://github.com/eea/volto-arcgis-block/compare/0.1.30...0.1.31)
|
|
8
33
|
|
|
34
|
+
> 11 February 2022
|
|
35
|
+
|
|
36
|
+
- Feature Time_Slider_Widget [`#100`](https://github.com/eea/volto-arcgis-block/pull/100)
|
|
9
37
|
- First commit [`#97`](https://github.com/eea/volto-arcgis-block/pull/97)
|
|
10
38
|
- REFACT: Delete comments [`a7a9296`](https://github.com/eea/volto-arcgis-block/commit/a7a929647749280ccf92d97ea17a3d743f1cfa20)
|
|
11
39
|
- FIX: Watch event on Time Extent for props and services. TimeSlider disabled if no time dimension [`2af7f50`](https://github.com/eea/volto-arcgis-block/commit/2af7f50d2ae1d5e033606d690bab0dffaa83b79e)
|
package/package.json
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React, { createRef } from 'react';
|
|
2
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
2
3
|
import { loadModules } from 'esri-loader';
|
|
3
4
|
import Highcharts from 'highcharts';
|
|
4
5
|
import HighchartsReact from 'highcharts-react-official';
|
|
5
|
-
|
|
6
|
+
import { Loader } from 'semantic-ui-react';
|
|
7
|
+
var GeometryEngine, Graphic, esriRequest;
|
|
6
8
|
|
|
7
9
|
class InfoWidget extends React.Component {
|
|
8
10
|
/**
|
|
@@ -15,7 +17,7 @@ class InfoWidget extends React.Component {
|
|
|
15
17
|
this.container = createRef();
|
|
16
18
|
//Initially, we set the state of the component to
|
|
17
19
|
//not be showing the basemap panel
|
|
18
|
-
this.state = { showMapMenu: false
|
|
20
|
+
this.state = { showMapMenu: false };
|
|
19
21
|
this.map = this.props.map;
|
|
20
22
|
this.menuClass =
|
|
21
23
|
'esri-icon-description esri-widget--button esri-widget esri-interactive';
|
|
@@ -23,11 +25,17 @@ class InfoWidget extends React.Component {
|
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
loader() {
|
|
26
|
-
return loadModules([
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
)
|
|
28
|
+
return loadModules([
|
|
29
|
+
'esri/geometry/geometryEngine',
|
|
30
|
+
'esri/Graphic',
|
|
31
|
+
'esri/request',
|
|
32
|
+
]).then(([_GeometryEngine, _Graphic, _esriRequest]) => {
|
|
33
|
+
[GeometryEngine, Graphic, esriRequest] = [
|
|
34
|
+
_GeometryEngine,
|
|
35
|
+
_Graphic,
|
|
36
|
+
_esriRequest,
|
|
37
|
+
];
|
|
38
|
+
});
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
/**
|
|
@@ -49,7 +57,6 @@ class InfoWidget extends React.Component {
|
|
|
49
57
|
showMapMenu: false,
|
|
50
58
|
pixelInfo: false,
|
|
51
59
|
popup: false,
|
|
52
|
-
timeLayers: {},
|
|
53
60
|
});
|
|
54
61
|
//this.props.view.popup.autoOpenEnabled = true;
|
|
55
62
|
this.removeMarker();
|
|
@@ -64,13 +71,11 @@ class InfoWidget extends React.Component {
|
|
|
64
71
|
// and ensure that the component is rendered again
|
|
65
72
|
this.setState({ showMapMenu: true });
|
|
66
73
|
this.props.mapViewer.view.popup.close();
|
|
67
|
-
//this.props.view.popup.autoOpenEnabled = false;
|
|
68
74
|
}
|
|
69
75
|
}
|
|
70
76
|
/**
|
|
71
77
|
* This method is executed after the rener method is executed
|
|
72
|
-
*/
|
|
73
|
-
async componentDidMount() {
|
|
78
|
+
*/ async componentDidMount() {
|
|
74
79
|
await this.loader();
|
|
75
80
|
this.props.view.ui.add(this.container.current, 'top-right');
|
|
76
81
|
this.props.view.on('click', (e) => {
|
|
@@ -78,72 +83,208 @@ class InfoWidget extends React.Component {
|
|
|
78
83
|
x: e.x,
|
|
79
84
|
y: e.y,
|
|
80
85
|
};
|
|
86
|
+
let layers;
|
|
81
87
|
if (this.props.mapViewer.activeWidget === this) {
|
|
82
|
-
|
|
88
|
+
this.setState({
|
|
89
|
+
loading: true,
|
|
90
|
+
});
|
|
91
|
+
layers = this.map.layers.items.filter(
|
|
83
92
|
(a) => a.visible && a.title !== 'nuts',
|
|
84
93
|
);
|
|
85
|
-
//let promises = [];
|
|
86
94
|
this.infoData = {};
|
|
95
|
+
let promises = [];
|
|
96
|
+
let layerTypes = [];
|
|
87
97
|
layers.forEach((layer, index) => {
|
|
88
98
|
let title = this.getLayerTitle(layer);
|
|
89
99
|
if (layer.isTimeSeries) {
|
|
90
100
|
if (layer.url.toLowerCase().includes('wms')) {
|
|
101
|
+
layerTypes.push({
|
|
102
|
+
isTimeSeries: true,
|
|
103
|
+
type: 'wms',
|
|
104
|
+
title: title,
|
|
105
|
+
});
|
|
106
|
+
promises.push(this.identifyWMS(layer, e));
|
|
91
107
|
} else if (layer.url.toLowerCase().includes('wmts')) {
|
|
108
|
+
layerTypes.push({
|
|
109
|
+
isTimeSeries: true,
|
|
110
|
+
type: 'wmts',
|
|
111
|
+
title: title,
|
|
112
|
+
});
|
|
92
113
|
} else {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
data: response,
|
|
98
|
-
};
|
|
99
|
-
this.setState({
|
|
100
|
-
pixelInfo: true,
|
|
101
|
-
});
|
|
114
|
+
layerTypes.push({
|
|
115
|
+
isTimeSeries: true,
|
|
116
|
+
type: 'featureLayer',
|
|
117
|
+
title: title,
|
|
102
118
|
});
|
|
119
|
+
promises.push(this.identify(layer, e));
|
|
103
120
|
}
|
|
104
121
|
} else {
|
|
105
122
|
if (layer.url.toLowerCase().includes('wms')) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
let properties = data.features[0].properties;
|
|
111
|
-
this.infoData[index] = {
|
|
112
|
-
title: title,
|
|
113
|
-
data: Object.entries(properties),
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
this.setState({
|
|
117
|
-
popup: true,
|
|
118
|
-
});
|
|
123
|
+
layerTypes.push({
|
|
124
|
+
isTimeSeries: false,
|
|
125
|
+
type: 'wms',
|
|
126
|
+
title: title,
|
|
119
127
|
});
|
|
128
|
+
promises.push(this.identifyWMS(layer, e));
|
|
120
129
|
} else if (layer.url.toLowerCase().includes('wmts')) {
|
|
130
|
+
layerTypes.push({
|
|
131
|
+
isTimeSeries: false,
|
|
132
|
+
type: 'wmts',
|
|
133
|
+
title: title,
|
|
134
|
+
});
|
|
121
135
|
} else {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
136
|
+
layerTypes.push({
|
|
137
|
+
isTimeSeries: false,
|
|
138
|
+
type: 'featureLayer',
|
|
139
|
+
title: title,
|
|
140
|
+
});
|
|
141
|
+
promises.push(this.props.view.hitTest(screenPoint));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
Promise.allSettled(promises).then((values) => {
|
|
145
|
+
if (promises.length === values.length) {
|
|
146
|
+
values.forEach((response, index) => {
|
|
147
|
+
let data = response.value;
|
|
148
|
+
let layer = layerTypes[index];
|
|
149
|
+
let properties = [];
|
|
150
|
+
if (layer.isTimeSeries) {
|
|
151
|
+
switch (layer.type) {
|
|
152
|
+
case 'wms':
|
|
153
|
+
if (data.type === 'FeatureCollection') {
|
|
154
|
+
if (data.features.length) {
|
|
155
|
+
let obj = data.features.map((a) => {
|
|
156
|
+
return a.properties;
|
|
157
|
+
});
|
|
158
|
+
properties = this.transformWmsData(obj);
|
|
159
|
+
}
|
|
160
|
+
} else if (data.doctype && data.doctype.name === 'html') {
|
|
161
|
+
let th = data.querySelectorAll('tbody th');
|
|
162
|
+
let tr = data.querySelectorAll(
|
|
163
|
+
'tbody tr:not(:first-of-type)',
|
|
164
|
+
);
|
|
165
|
+
if (th.length) {
|
|
166
|
+
let obj = Array.from(tr).map((a) => {
|
|
167
|
+
let x = [];
|
|
168
|
+
a.querySelectorAll('td').forEach((td, index) => {
|
|
169
|
+
x[th[index].textContent] = td.textContent;
|
|
170
|
+
});
|
|
171
|
+
return x;
|
|
172
|
+
});
|
|
173
|
+
properties = this.transformWmsData(obj);
|
|
174
|
+
}
|
|
175
|
+
} else if (
|
|
176
|
+
data.getElementsByTagName('FIELDS').length &&
|
|
177
|
+
typeof data !== 'undefined'
|
|
178
|
+
) {
|
|
179
|
+
let fields = data.getElementsByTagName('FIELDS');
|
|
180
|
+
if (fields.length) {
|
|
181
|
+
let obj = Array.from(fields).map((a) => {
|
|
182
|
+
let x = [];
|
|
183
|
+
Object.entries(a.attributes).forEach((b) => {
|
|
184
|
+
x[b[1].name] = b[1].value;
|
|
185
|
+
});
|
|
186
|
+
return x;
|
|
187
|
+
});
|
|
188
|
+
properties = this.transformWmsData(obj);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
this.infoData[index] = {
|
|
192
|
+
title: layer.title,
|
|
193
|
+
data: properties,
|
|
194
|
+
time: true,
|
|
195
|
+
};
|
|
196
|
+
break;
|
|
197
|
+
case 'wmts':
|
|
198
|
+
this.infoData[index] = {
|
|
199
|
+
title: layer.title,
|
|
200
|
+
data: properties,
|
|
201
|
+
time: true,
|
|
202
|
+
};
|
|
203
|
+
break;
|
|
204
|
+
case 'featureLayer':
|
|
205
|
+
this.infoData[index] = {
|
|
206
|
+
title: layer.title,
|
|
207
|
+
data: data,
|
|
208
|
+
time: true,
|
|
209
|
+
};
|
|
210
|
+
break;
|
|
211
|
+
default:
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
switch (layer.type) {
|
|
216
|
+
case 'wms':
|
|
217
|
+
if (data.type === 'FeatureCollection') {
|
|
218
|
+
if (data.features.length) {
|
|
219
|
+
properties = data.features[0].properties;
|
|
220
|
+
properties = Object.entries(properties);
|
|
221
|
+
}
|
|
222
|
+
} else if (data.doctype && data.doctype.name === 'html') {
|
|
223
|
+
let th = data.querySelectorAll('tbody th');
|
|
224
|
+
let td = data.querySelectorAll('tbody td');
|
|
225
|
+
if (th.length) {
|
|
226
|
+
let fields = [];
|
|
227
|
+
th.forEach((item, index) => {
|
|
228
|
+
fields.push([
|
|
229
|
+
item.textContent,
|
|
230
|
+
td[index].textContent,
|
|
231
|
+
]);
|
|
232
|
+
});
|
|
233
|
+
properties = fields;
|
|
234
|
+
}
|
|
235
|
+
} else if (
|
|
236
|
+
data.getElementsByTagName('FIELDS').length &&
|
|
237
|
+
typeof data !== 'undefined'
|
|
238
|
+
) {
|
|
239
|
+
let fields = data.getElementsByTagName('FIELDS');
|
|
240
|
+
if (fields.length) {
|
|
241
|
+
properties = Object.entries(fields[0].attributes).map(
|
|
242
|
+
(a) => {
|
|
243
|
+
return [a[1].name, a[1].value];
|
|
244
|
+
},
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
this.infoData[index] = {
|
|
249
|
+
title: layer.title,
|
|
250
|
+
data: properties,
|
|
251
|
+
};
|
|
252
|
+
break;
|
|
253
|
+
case 'wmts':
|
|
254
|
+
this.infoData[index] = {
|
|
255
|
+
title: layer.title,
|
|
256
|
+
data: properties,
|
|
257
|
+
};
|
|
258
|
+
break;
|
|
259
|
+
case 'featureLayer':
|
|
260
|
+
if (data.results.length) {
|
|
261
|
+
var graphic = data.results.filter((result) => {
|
|
262
|
+
return result.graphic.layer === layers[index];
|
|
263
|
+
})[0].graphic;
|
|
264
|
+
if (graphic) {
|
|
265
|
+
properties = graphic.attributes;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
this.infoData[index] = {
|
|
269
|
+
title: layer.title,
|
|
270
|
+
data: Object.entries(properties),
|
|
271
|
+
};
|
|
272
|
+
break;
|
|
273
|
+
default:
|
|
274
|
+
break;
|
|
133
275
|
}
|
|
134
276
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
277
|
+
});
|
|
278
|
+
let layerIndex = layers.length - 1;
|
|
279
|
+
this.setState({
|
|
280
|
+
layerIndex: layerIndex,
|
|
281
|
+
pixelInfo: layerTypes[layerIndex].isTimeSeries ? true : false,
|
|
282
|
+
popup: !layerTypes[layerIndex].isTimeSeries ? true : false,
|
|
283
|
+
loading: false,
|
|
138
284
|
});
|
|
139
285
|
}
|
|
140
|
-
}
|
|
286
|
+
});
|
|
141
287
|
this.addMarker(e);
|
|
142
|
-
// Promise.all(promises).then((values) => {
|
|
143
|
-
// this.setState({
|
|
144
|
-
// popup: true,
|
|
145
|
-
// });
|
|
146
|
-
// });
|
|
147
288
|
});
|
|
148
289
|
}
|
|
149
290
|
});
|
|
@@ -161,36 +302,158 @@ class InfoWidget extends React.Component {
|
|
|
161
302
|
return title;
|
|
162
303
|
}
|
|
163
304
|
|
|
164
|
-
|
|
165
|
-
let
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
305
|
+
getLayerName(layer) {
|
|
306
|
+
let title;
|
|
307
|
+
if (layer.sublayers) {
|
|
308
|
+
title = layer.sublayers.items[0].name;
|
|
309
|
+
} else if (layer.activeLayer) {
|
|
310
|
+
title = layer.activeLayer.name;
|
|
311
|
+
} else {
|
|
312
|
+
title = layer.name;
|
|
313
|
+
}
|
|
314
|
+
return title;
|
|
174
315
|
}
|
|
175
316
|
|
|
176
|
-
|
|
177
|
-
let
|
|
178
|
-
let
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
317
|
+
identifyWMS(layer, event) {
|
|
318
|
+
let layerId = this.getLayerName(layer);
|
|
319
|
+
let url = layer.featureInfoUrl ? layer.featureInfoUrl : layer.url;
|
|
320
|
+
return this.wmsCapabilities(url).then((xml) => {
|
|
321
|
+
let version = this.parseCapabilities(xml, 'wms_capabilities')[0]
|
|
322
|
+
.attributes['version'];
|
|
323
|
+
let format = this.parseFormat(xml, layerId);
|
|
324
|
+
let times = '';
|
|
325
|
+
let nTimes = 1;
|
|
326
|
+
if (layer.isTimeSeries) {
|
|
327
|
+
times = this.parseTime(xml, layerId);
|
|
328
|
+
nTimes = times.length;
|
|
329
|
+
}
|
|
330
|
+
return esriRequest(url, {
|
|
331
|
+
responseType: 'html',
|
|
332
|
+
sync: 'true',
|
|
333
|
+
query: {
|
|
334
|
+
request: 'GetFeatureInfo',
|
|
335
|
+
service: 'WMS',
|
|
336
|
+
version: version,
|
|
337
|
+
SRS: 'EPSG:' + this.props.view.spatialReference.latestWkid,
|
|
338
|
+
CRS: 'EPSG:' + this.props.view.spatialReference.latestWkid,
|
|
339
|
+
BBOX:
|
|
340
|
+
'' +
|
|
341
|
+
this.props.view.extent.xmin +
|
|
342
|
+
', ' +
|
|
343
|
+
this.props.view.extent.ymin +
|
|
344
|
+
', ' +
|
|
345
|
+
this.props.view.extent.xmax +
|
|
346
|
+
', ' +
|
|
347
|
+
this.props.view.extent.ymax,
|
|
348
|
+
HEIGHT: this.props.view.height,
|
|
349
|
+
WIDTH: this.props.view.width,
|
|
350
|
+
X: event.screenPoint.x,
|
|
351
|
+
Y: event.screenPoint.y,
|
|
352
|
+
QUERY_LAYERS: layerId,
|
|
353
|
+
INFO_FORMAT: format,
|
|
354
|
+
TIME: times ? times[0] + '/' + times[nTimes - 1] : '',
|
|
355
|
+
FEATURE_COUNT: '' + nTimes,
|
|
356
|
+
},
|
|
357
|
+
})
|
|
358
|
+
.then((response) => {
|
|
359
|
+
let format = response.requestOptions.query.INFO_FORMAT;
|
|
360
|
+
let data;
|
|
361
|
+
if (format.includes('text')) {
|
|
362
|
+
data = new window.DOMParser().parseFromString(
|
|
363
|
+
response.data,
|
|
364
|
+
'text/html',
|
|
365
|
+
);
|
|
366
|
+
} else if (format.includes('json')) {
|
|
367
|
+
data = JSON.parse(response.data);
|
|
368
|
+
}
|
|
369
|
+
return data;
|
|
185
370
|
})
|
|
186
|
-
.
|
|
187
|
-
return
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
return
|
|
371
|
+
.then((data) => {
|
|
372
|
+
return data;
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
wmsCapabilities(url) {
|
|
378
|
+
return esriRequest(url, {
|
|
379
|
+
responseType: 'html',
|
|
380
|
+
sync: 'true',
|
|
381
|
+
query: {
|
|
382
|
+
request: 'GetCapabilities',
|
|
383
|
+
service: 'WMS',
|
|
384
|
+
},
|
|
385
|
+
}).then((response) => {
|
|
386
|
+
let parser = new DOMParser();
|
|
387
|
+
let xml = parser.parseFromString(response.data, 'text/html');
|
|
388
|
+
return xml;
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
parseCapabilities(xml, tag) {
|
|
393
|
+
return xml.getElementsByTagName(tag);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
parseFormat(xml) {
|
|
397
|
+
let formats = Array.from(
|
|
398
|
+
Array.from(this.parseCapabilities(xml, 'getFeatureInfo')).map(
|
|
399
|
+
(f) => f.children,
|
|
400
|
+
)[0],
|
|
401
|
+
).map((v) => v.textContent);
|
|
402
|
+
let format = formats.filter((v) => v.includes('json'))[0];
|
|
403
|
+
if (!format) format = formats.filter((v) => v.includes('html'))[0];
|
|
404
|
+
if (!format) format = formats.filter((v) => v.includes('text/xml'))[0];
|
|
405
|
+
if (!format) format = formats[0];
|
|
406
|
+
return format;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
parseTime(xml, layerId) {
|
|
410
|
+
let layers = Array.from(xml.querySelectorAll('Layer')).filter(
|
|
411
|
+
(v) => v.querySelectorAll('Layer').length === 0,
|
|
412
|
+
);
|
|
413
|
+
let layer = layers.find((a) => {
|
|
414
|
+
return a.querySelector('Name').innerText === layerId;
|
|
415
|
+
});
|
|
416
|
+
let times = layer.querySelector('Dimension').innerText.split(',');
|
|
417
|
+
return times;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
transformWmsData(obj) {
|
|
421
|
+
let values = { timeFields: {}, data: {}, variables: {} };
|
|
422
|
+
let startField = Object.keys(obj[0]).find(
|
|
423
|
+
(a, i) =>
|
|
424
|
+
a.toUpperCase().includes('DATE') &&
|
|
425
|
+
!isNaN(parseInt(Object.values(obj[0])[i])),
|
|
426
|
+
);
|
|
427
|
+
values.timeFields['start'] = startField;
|
|
428
|
+
let fields = Object.keys(obj[0]).filter((a, i) => {
|
|
429
|
+
return (
|
|
430
|
+
!isNaN(parseInt(Object.values(obj[0])[i])) &&
|
|
431
|
+
a.toUpperCase() !== 'OBJECTID' &&
|
|
432
|
+
!a.toUpperCase().includes('DATE')
|
|
433
|
+
);
|
|
434
|
+
});
|
|
435
|
+
let field = fields[0];
|
|
436
|
+
values.variables = { options: fields, selected: field };
|
|
437
|
+
values.timeFields['values'] = obj.map((a, i) => {
|
|
438
|
+
let date = {};
|
|
439
|
+
Object.entries(obj[i]).forEach(([key, value]) => {
|
|
440
|
+
if (key === startField) {
|
|
441
|
+
date[key] = value;
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
return date;
|
|
445
|
+
});
|
|
446
|
+
values.data['outFields'] = field;
|
|
447
|
+
values.data['values'] = obj.map((a, i) => {
|
|
448
|
+
let x = {};
|
|
449
|
+
Object.entries(obj[i]).forEach(([key, value]) => {
|
|
450
|
+
if (fields.includes(key)) {
|
|
451
|
+
x[key] = parseFloat(value);
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
return x;
|
|
455
|
+
});
|
|
456
|
+
return values;
|
|
194
457
|
}
|
|
195
458
|
|
|
196
459
|
addMarker(evt) {
|
|
@@ -262,7 +525,9 @@ class InfoWidget extends React.Component {
|
|
|
262
525
|
let p1 = layer.queryFeatures(timeQuery).then((r) => {
|
|
263
526
|
let timevals = [];
|
|
264
527
|
r.features.forEach((e) => timevals.push(e.attributes));
|
|
265
|
-
values.timeFields['values'] = timevals
|
|
528
|
+
values.timeFields['values'] = timevals.sort((a, b) => {
|
|
529
|
+
return a[values.timeFields.start] - b[values.timeFields.start];
|
|
530
|
+
});
|
|
266
531
|
});
|
|
267
532
|
|
|
268
533
|
//Query for data
|
|
@@ -309,13 +574,27 @@ class InfoWidget extends React.Component {
|
|
|
309
574
|
});
|
|
310
575
|
}
|
|
311
576
|
|
|
312
|
-
|
|
577
|
+
loadInfoChart(index) {
|
|
578
|
+
let response = this.infoData[index].data;
|
|
579
|
+
let variable = response.variables.selected;
|
|
580
|
+
let data = {
|
|
581
|
+
x: response.timeFields.values.map((a) => {
|
|
582
|
+
return a[response.timeFields.start];
|
|
583
|
+
}),
|
|
584
|
+
y: response.data.values.map((a) => {
|
|
585
|
+
return Math.round(a[variable] * 100) / 100;
|
|
586
|
+
}),
|
|
587
|
+
};
|
|
588
|
+
return this.createChart(variable, data);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
createChart(variable, chartData) {
|
|
313
592
|
let chartOptions = {
|
|
314
593
|
chart: {
|
|
315
594
|
height: 208,
|
|
316
595
|
},
|
|
317
596
|
title: {
|
|
318
|
-
text:
|
|
597
|
+
text: '',
|
|
319
598
|
style: {},
|
|
320
599
|
},
|
|
321
600
|
subtitle: {
|
|
@@ -389,7 +668,7 @@ class InfoWidget extends React.Component {
|
|
|
389
668
|
return (
|
|
390
669
|
<tr key={item}>
|
|
391
670
|
{Object.values(item).map((val) => (
|
|
392
|
-
<td>{val}</td>
|
|
671
|
+
<td key={val}>{val}</td>
|
|
393
672
|
))}
|
|
394
673
|
</tr>
|
|
395
674
|
);
|
|
@@ -403,7 +682,6 @@ class InfoWidget extends React.Component {
|
|
|
403
682
|
|
|
404
683
|
loadVariableSelector(index) {
|
|
405
684
|
let response = this.infoData[index].data;
|
|
406
|
-
//let title = this.infoData[index].title;
|
|
407
685
|
let variables = response.variables.options;
|
|
408
686
|
let variable = response.variables.selected;
|
|
409
687
|
let options = variables.map((option) => {
|
|
@@ -429,17 +707,120 @@ class InfoWidget extends React.Component {
|
|
|
429
707
|
);
|
|
430
708
|
}
|
|
431
709
|
|
|
710
|
+
loadStatisticsSelector(index) {
|
|
711
|
+
let statistics = ['Mean', 'Median', 'Variance', 'Standard deviation'];
|
|
712
|
+
let selected =
|
|
713
|
+
this.infoData[index].data.statistics &&
|
|
714
|
+
this.infoData[index].data.statistics.selected
|
|
715
|
+
? this.infoData[index].data.statistics.selected
|
|
716
|
+
: statistics[0];
|
|
717
|
+
let value =
|
|
718
|
+
this.infoData[index].data.statistics &&
|
|
719
|
+
this.infoData[index].data.statistics.value
|
|
720
|
+
? this.infoData[index].data.statistics.value
|
|
721
|
+
: this.calculateStatistics(selected);
|
|
722
|
+
this.infoData[index].data.statistics = {
|
|
723
|
+
selected: selected,
|
|
724
|
+
value: value,
|
|
725
|
+
};
|
|
726
|
+
let options = statistics.map((option) => {
|
|
727
|
+
return (
|
|
728
|
+
<option key={option} value={option}>
|
|
729
|
+
{option}
|
|
730
|
+
</option>
|
|
731
|
+
);
|
|
732
|
+
});
|
|
733
|
+
return (
|
|
734
|
+
<>
|
|
735
|
+
<label>
|
|
736
|
+
Statistics
|
|
737
|
+
<select
|
|
738
|
+
className="esri-select"
|
|
739
|
+
id="info_statistics"
|
|
740
|
+
value={selected}
|
|
741
|
+
onBlur={(e) => e.preventDefault()}
|
|
742
|
+
onChange={(e) => this.selectStatistics(e.target.value)}
|
|
743
|
+
>
|
|
744
|
+
{options}
|
|
745
|
+
</select>
|
|
746
|
+
</label>
|
|
747
|
+
<div className="info-statistics-panel">
|
|
748
|
+
{this.infoData[index].data.statistics.value && (
|
|
749
|
+
<span className="info-statistics-result">
|
|
750
|
+
{this.infoData[index].data.statistics.value}
|
|
751
|
+
</span>
|
|
752
|
+
)}
|
|
753
|
+
</div>
|
|
754
|
+
</>
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
calculateStatistics(statistic) {
|
|
759
|
+
let index = this.state.layerIndex;
|
|
760
|
+
let response = this.infoData[index].data;
|
|
761
|
+
let variable = response.variables.selected;
|
|
762
|
+
let data = response.data.values.map((a) => {
|
|
763
|
+
return a[variable];
|
|
764
|
+
});
|
|
765
|
+
let mean = data.reduce((a, b) => a + b) / data.length;
|
|
766
|
+
let result;
|
|
767
|
+
switch (statistic) {
|
|
768
|
+
case 'Mean':
|
|
769
|
+
result = mean;
|
|
770
|
+
break;
|
|
771
|
+
case 'Median':
|
|
772
|
+
data = data.sort((a, b) => {
|
|
773
|
+
return a - b;
|
|
774
|
+
});
|
|
775
|
+
var i = data.length / 2;
|
|
776
|
+
result =
|
|
777
|
+
i % 1 === 0 ? (data[i - 1] + data[i]) / 2 : data[Math.floor(i)];
|
|
778
|
+
break;
|
|
779
|
+
case 'Variance':
|
|
780
|
+
result =
|
|
781
|
+
data.map((x) => Math.pow(x - mean, 2)).reduce((a, b) => a + b) /
|
|
782
|
+
data.length;
|
|
783
|
+
break;
|
|
784
|
+
case 'Standard deviation':
|
|
785
|
+
let variance =
|
|
786
|
+
data.map((x) => Math.pow(x - mean, 2)).reduce((a, b) => a + b) /
|
|
787
|
+
data.length;
|
|
788
|
+
result = Math.sqrt(variance);
|
|
789
|
+
break;
|
|
790
|
+
default:
|
|
791
|
+
break;
|
|
792
|
+
}
|
|
793
|
+
return result;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
selectStatistics(option) {
|
|
797
|
+
let index = this.state.layerIndex;
|
|
798
|
+
this.infoData[index].data.statistics = {
|
|
799
|
+
selected: option,
|
|
800
|
+
value: this.calculateStatistics(option),
|
|
801
|
+
};
|
|
802
|
+
this.setState({
|
|
803
|
+
pixelInfo: true,
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
|
|
432
807
|
/**
|
|
433
808
|
* This method renders the component
|
|
434
809
|
* @returns jsx
|
|
435
810
|
*/
|
|
436
811
|
render() {
|
|
437
|
-
let noData =
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
812
|
+
let noData = true;
|
|
813
|
+
if (this.state.pixelInfo) {
|
|
814
|
+
noData = this.infoData[this.state.layerIndex].data.data
|
|
815
|
+
? this.infoData[this.state.layerIndex].data.data.values.map((a) => {
|
|
816
|
+
return a[
|
|
817
|
+
this.infoData[this.state.layerIndex].data.variables.selected
|
|
818
|
+
];
|
|
819
|
+
}).length === 0
|
|
820
|
+
: true;
|
|
821
|
+
} else if (this.state.popup) {
|
|
822
|
+
noData = this.infoData[this.state.layerIndex].data.length === 0 && true;
|
|
823
|
+
}
|
|
443
824
|
return (
|
|
444
825
|
<>
|
|
445
826
|
<div ref={this.container} className="info-container">
|
|
@@ -453,29 +834,84 @@ class InfoWidget extends React.Component {
|
|
|
453
834
|
role="button"
|
|
454
835
|
></div>
|
|
455
836
|
<div className="info-panel">
|
|
456
|
-
{
|
|
457
|
-
<
|
|
458
|
-
|
|
459
|
-
</span>
|
|
460
|
-
)}
|
|
461
|
-
{this.state.pixelInfo && !noData && (
|
|
837
|
+
{this.state.loading ? (
|
|
838
|
+
<Loader active inline="centered" size="small" />
|
|
839
|
+
) : (
|
|
462
840
|
<>
|
|
463
|
-
{this.
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
841
|
+
{(this.state.pixelInfo || this.state.popup) && (
|
|
842
|
+
<>
|
|
843
|
+
<div className="info-panel-buttons">
|
|
844
|
+
<button
|
|
845
|
+
className="ccl-button ccl-button--default info-button-left"
|
|
846
|
+
onClick={() =>
|
|
847
|
+
this.setState({
|
|
848
|
+
layerIndex: this.state.layerIndex + 1,
|
|
849
|
+
pixelInfo: this.infoData[this.state.layerIndex + 1]
|
|
850
|
+
.time
|
|
851
|
+
? true
|
|
852
|
+
: false,
|
|
853
|
+
popup: !this.infoData[this.state.layerIndex + 1]
|
|
854
|
+
.time
|
|
855
|
+
? true
|
|
856
|
+
: false,
|
|
857
|
+
})
|
|
858
|
+
}
|
|
859
|
+
disabled={
|
|
860
|
+
this.state.layerIndex ===
|
|
861
|
+
Object.keys(this.infoData).length - 1
|
|
862
|
+
}
|
|
863
|
+
>
|
|
864
|
+
<FontAwesomeIcon icon={['fas', 'chevron-left']} />
|
|
865
|
+
<span>Previous layer</span>
|
|
866
|
+
</button>
|
|
867
|
+
<button
|
|
868
|
+
className="ccl-button ccl-button--default info-button-right"
|
|
869
|
+
onClick={() =>
|
|
870
|
+
this.setState({
|
|
871
|
+
layerIndex: this.state.layerIndex - 1,
|
|
872
|
+
pixelInfo: this.infoData[this.state.layerIndex - 1]
|
|
873
|
+
.time
|
|
874
|
+
? true
|
|
875
|
+
: false,
|
|
876
|
+
popup: !this.infoData[this.state.layerIndex - 1]
|
|
877
|
+
.time
|
|
878
|
+
? true
|
|
879
|
+
: false,
|
|
880
|
+
})
|
|
881
|
+
}
|
|
882
|
+
disabled={this.state.layerIndex === 0}
|
|
883
|
+
>
|
|
884
|
+
<span>Next layer</span>
|
|
885
|
+
<FontAwesomeIcon icon={['fas', 'chevron-right']} />
|
|
886
|
+
</button>
|
|
887
|
+
</div>
|
|
888
|
+
<span className="info-panel-title">
|
|
889
|
+
{this.infoData[this.state.layerIndex].title}
|
|
890
|
+
</span>
|
|
891
|
+
</>
|
|
892
|
+
)}
|
|
893
|
+
{this.state.pixelInfo && !noData && (
|
|
894
|
+
<>
|
|
895
|
+
{this.loadVariableSelector(this.state.layerIndex)}
|
|
896
|
+
<HighchartsReact
|
|
897
|
+
highcharts={Highcharts}
|
|
898
|
+
options={this.loadInfoChart(this.state.layerIndex)}
|
|
899
|
+
/>
|
|
900
|
+
{this.loadStatisticsSelector(this.state.layerIndex)}
|
|
901
|
+
</>
|
|
902
|
+
)}
|
|
903
|
+
{this.state.popup &&
|
|
904
|
+
!noData &&
|
|
905
|
+
this.loadInfoTable(this.state.layerIndex)}
|
|
906
|
+
{this.state.pixelInfo || this.state.popup ? (
|
|
907
|
+
noData && <span className="info-panel-empty">No data</span>
|
|
908
|
+
) : (
|
|
909
|
+
<span className="info-panel-empty">
|
|
910
|
+
Click on the map to get pixel info
|
|
911
|
+
</span>
|
|
912
|
+
)}
|
|
469
913
|
</>
|
|
470
914
|
)}
|
|
471
|
-
{this.state.popup && !noData && this.loadInfoTable(layer)}
|
|
472
|
-
{this.state.pixelInfo || this.state.popup ? (
|
|
473
|
-
noData && <span className="info-panel-empty">No data</span>
|
|
474
|
-
) : (
|
|
475
|
-
<span className="info-panel-empty">
|
|
476
|
-
Click on the map to get pixel info
|
|
477
|
-
</span>
|
|
478
|
-
)}
|
|
479
915
|
</div>
|
|
480
916
|
</div>
|
|
481
917
|
</>
|
|
@@ -273,15 +273,13 @@ export const AddCartItem = ({
|
|
|
273
273
|
</div>
|
|
274
274
|
</Modal.Actions>
|
|
275
275
|
</Modal>
|
|
276
|
-
<
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
/>
|
|
284
|
-
</div>
|
|
276
|
+
<FontAwesomeIcon
|
|
277
|
+
className={'map-menu-icon' + (isLoggedIn ? '' : ' locked')}
|
|
278
|
+
icon={['fas', 'download']}
|
|
279
|
+
onClick={() => {
|
|
280
|
+
isLoggedIn && showModal();
|
|
281
|
+
}}
|
|
282
|
+
/>
|
|
285
283
|
</>
|
|
286
284
|
)}
|
|
287
285
|
</>
|
|
@@ -615,6 +613,7 @@ class MenuWidget extends React.Component {
|
|
|
615
613
|
index,
|
|
616
614
|
inheritedIndexDataset,
|
|
617
615
|
dataset.ViewService,
|
|
616
|
+
dataset.TimeSeriesService,
|
|
618
617
|
checkIndex,
|
|
619
618
|
dataset.IsTimeSeries,
|
|
620
619
|
layer_default,
|
|
@@ -659,16 +658,24 @@ class MenuWidget extends React.Component {
|
|
|
659
658
|
>
|
|
660
659
|
<span>{dataset.DatasetTitle}</span>
|
|
661
660
|
</label>
|
|
662
|
-
|
|
663
|
-
<
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
661
|
+
<div className="map-menu-icons">
|
|
662
|
+
<a href={dataset.DatasetURL} target="_blank" rel="noreferrer">
|
|
663
|
+
<FontAwesomeIcon
|
|
664
|
+
className="map-menu-icon"
|
|
665
|
+
icon={['fa', 'info-circle']}
|
|
666
|
+
/>
|
|
667
|
+
</a>
|
|
668
|
+
{!this.props.download && dataset.Downloadable && (
|
|
669
|
+
<AddCartItem
|
|
670
|
+
cartData={this.compCfg}
|
|
671
|
+
props={this.props}
|
|
672
|
+
mapViewer={this.props.mapViewer}
|
|
673
|
+
download={this.props.download}
|
|
674
|
+
areaData={this.props.area}
|
|
675
|
+
dataset={dataset}
|
|
676
|
+
/>
|
|
677
|
+
)}
|
|
678
|
+
</div>
|
|
672
679
|
</div>
|
|
673
680
|
<div
|
|
674
681
|
className="ccl-form map-menu-layers-container"
|
|
@@ -711,6 +718,7 @@ class MenuWidget extends React.Component {
|
|
|
711
718
|
layerIndex,
|
|
712
719
|
inheritedIndex,
|
|
713
720
|
urlWMS,
|
|
721
|
+
featureInfoUrl,
|
|
714
722
|
parentIndex,
|
|
715
723
|
isTimeSeries,
|
|
716
724
|
layer_default,
|
|
@@ -742,6 +750,7 @@ class MenuWidget extends React.Component {
|
|
|
742
750
|
visible: true,
|
|
743
751
|
legendEnabled: true,
|
|
744
752
|
legendUrl: urlWMS + legendRequest + layer.LayerId,
|
|
753
|
+
featureInfoUrl: featureInfoUrl,
|
|
745
754
|
},
|
|
746
755
|
],
|
|
747
756
|
isTimeSeries: isTimeSeries,
|
|
@@ -754,6 +763,7 @@ class MenuWidget extends React.Component {
|
|
|
754
763
|
activeLayer: {
|
|
755
764
|
id: layer.LayerId,
|
|
756
765
|
title: layer.Title,
|
|
766
|
+
featureInfoUrl: featureInfoUrl,
|
|
757
767
|
},
|
|
758
768
|
isTimeSeries: isTimeSeries,
|
|
759
769
|
});
|
|
@@ -764,6 +774,7 @@ class MenuWidget extends React.Component {
|
|
|
764
774
|
url: urlWMS + (urlWMS.endsWith('/') ? '' : '/') + layer.LayerId,
|
|
765
775
|
id: layer.LayerId,
|
|
766
776
|
title: layer.Title,
|
|
777
|
+
featureInfoUrl: featureInfoUrl,
|
|
767
778
|
popupEnabled: true,
|
|
768
779
|
isTimeSeries: isTimeSeries,
|
|
769
780
|
});
|
|
@@ -1216,10 +1227,10 @@ class MenuWidget extends React.Component {
|
|
|
1216
1227
|
}
|
|
1217
1228
|
});
|
|
1218
1229
|
if (layers.length === 0 && document.querySelector('.info-container')) {
|
|
1219
|
-
|
|
1220
|
-
|
|
1230
|
+
this.props.mapViewer.closeActiveWidget();
|
|
1231
|
+
document.querySelector('.info-container').style.display = 'none';
|
|
1221
1232
|
} else if (layers.length > 0) {
|
|
1222
|
-
|
|
1233
|
+
document.querySelector('.info-container').style.display = 'flex';
|
|
1223
1234
|
}
|
|
1224
1235
|
}
|
|
1225
1236
|
|
|
@@ -425,7 +425,7 @@
|
|
|
425
425
|
.map-menu-dataset > div:first-of-type {
|
|
426
426
|
display: flex;
|
|
427
427
|
width: 100%;
|
|
428
|
-
justify-content:
|
|
428
|
+
justify-content: flex-start;
|
|
429
429
|
}
|
|
430
430
|
|
|
431
431
|
.map-menu .map-menu-layers-container {
|
|
@@ -595,7 +595,7 @@
|
|
|
595
595
|
overflow: auto;
|
|
596
596
|
}
|
|
597
597
|
|
|
598
|
-
/*
|
|
598
|
+
/* Info widget */
|
|
599
599
|
.info-container {
|
|
600
600
|
display: none;
|
|
601
601
|
box-shadow: none !important;
|
|
@@ -611,6 +611,29 @@
|
|
|
611
611
|
font-size: 1rem;
|
|
612
612
|
}
|
|
613
613
|
|
|
614
|
+
.info-panel-buttons {
|
|
615
|
+
display: flex;
|
|
616
|
+
justify-content: space-between;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
.info-panel-buttons button {
|
|
620
|
+
padding: 0.5rem 1rem;
|
|
621
|
+
margin-bottom: 1rem;
|
|
622
|
+
font-size: 0.875rem;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
.info-panel-buttons .info-button-left svg {
|
|
626
|
+
margin-right: 0.25rem;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
.info-panel-buttons .info-button-right svg {
|
|
630
|
+
margin-left: 0.25rem;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
.info-panel-buttons button svg {
|
|
634
|
+
color: inherit;
|
|
635
|
+
}
|
|
636
|
+
|
|
614
637
|
.info-panel-title {
|
|
615
638
|
display: block;
|
|
616
639
|
margin-bottom: 1rem;
|
|
@@ -635,11 +658,11 @@
|
|
|
635
658
|
}
|
|
636
659
|
|
|
637
660
|
.info-table tr:nth-child(even) {
|
|
638
|
-
background-color: #
|
|
661
|
+
background-color: #ebebeb33;
|
|
639
662
|
}
|
|
640
663
|
|
|
641
664
|
.info-table tr:nth-child(odd) {
|
|
642
|
-
background-color: #
|
|
665
|
+
background-color: #ebebeb;
|
|
643
666
|
}
|
|
644
667
|
|
|
645
668
|
/* Time slider*/
|