@eeacms/volto-arcgis-block 0.1.23 → 0.1.24

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,24 @@ 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.24](https://github.com/eea/volto-arcgis-block/compare/0.1.23...0.1.24)
8
+
9
+ - Bugs n improvements [`#80`](https://github.com/eea/volto-arcgis-block/pull/80)
10
+ - The area array to a json object and added a unique_id to the cart [`#79`](https://github.com/eea/volto-arcgis-block/pull/79)
11
+ - Remove Global from legend [`#76`](https://github.com/eea/volto-arcgis-block/pull/76)
12
+ - ESLint fix [`974d62b`](https://github.com/eea/volto-arcgis-block/commit/974d62bd5b66025e12fa85dffcd0d1a5ae04cba4)
13
+ - ESLint fix [`5c4b0d7`](https://github.com/eea/volto-arcgis-block/commit/5c4b0d76de50edd85809c35449f2fb364a9e9312)
14
+ - ESLint fix [`d57d144`](https://github.com/eea/volto-arcgis-block/commit/d57d144fd0e3dbbbbc95195f289d50e6c4b03375)
15
+ - ESLint fix [`9eb0a0c`](https://github.com/eea/volto-arcgis-block/commit/9eb0a0c17d018ac1b25bde422ac149b46b146ff4)
16
+ - ESLint fix [`5fd3772`](https://github.com/eea/volto-arcgis-block/commit/5fd3772f197a3a380067962a5638dbf901a4624e)
17
+ - Pixel info [`1ccef39`](https://github.com/eea/volto-arcgis-block/commit/1ccef39a9adcc3ee94655c77a5a166b613600a08)
18
+ - NUTS bug fix [`37fe7f3`](https://github.com/eea/volto-arcgis-block/commit/37fe7f3fad2c005e3b7c234b3b0564d5df92fa8c)
19
+
7
20
  #### [0.1.23](https://github.com/eea/volto-arcgis-block/compare/0.1.22...0.1.23)
8
21
 
22
+ > 17 December 2021
23
+
24
+ - Develop [`#75`](https://github.com/eea/volto-arcgis-block/pull/75)
9
25
  - Use cases region highlight [`#71`](https://github.com/eea/volto-arcgis-block/pull/71)
10
26
  - Bugs n improvements [`#74`](https://github.com/eea/volto-arcgis-block/pull/74)
11
27
  - Add NUTS 2 in the Map viewer [`#72`](https://github.com/eea/volto-arcgis-block/pull/72)
package/Jenkinsfile CHANGED
@@ -4,7 +4,7 @@ pipeline {
4
4
  environment {
5
5
  GIT_NAME = "volto-arcgis-block"
6
6
  NAMESPACE = "@eeacms"
7
- SONARQUBE_TAGS = "volto.eea.europa.eu,clms.land.copernicus.eu"
7
+ SONARQUBE_TAGS = "volto.eea.europa.eu,clms.land.copernicus.eu,water.europa.eu-freshwater"
8
8
  DEPENDENCIES = ""
9
9
  }
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-arcgis-block",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
4
4
  "description": "volto-arcgis-block: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: CodeSyntax",
@@ -40,7 +40,9 @@
40
40
  "@fortawesome/fontawesome-svg-core": "1.2.35",
41
41
  "@fortawesome/free-solid-svg-icons": "5.15.3",
42
42
  "@fortawesome/react-fontawesome": "0.1.14",
43
- "@eeacms/volto-clms-utils": "0.1.1"
43
+ "@eeacms/volto-clms-utils": "0.1.1",
44
+ "highcharts": "^9.3.2",
45
+ "highcharts-react-official": "^3.1.0"
44
46
  },
45
47
  "devDependencies": {
46
48
  "@cypress/code-coverage": "^3.9.5",
@@ -1,7 +1,4 @@
1
1
  import React, { createRef } from 'react';
2
- //import "@arcgis/core/assets/esri/css/main.css";
3
- //import "./css/ArcgisMap.css";
4
- //import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
5
2
  import { loadModules } from 'esri-loader';
6
3
 
7
4
  var Graphic,
@@ -202,7 +199,14 @@ class AreaWidget extends React.Component {
202
199
  });
203
200
  this.props.map.add(this.nutsGroupLayer);
204
201
  this.props.view.on('click', (event) => {
205
- if (this.props.mapViewer.activeWidget === this) {
202
+ if (
203
+ (this.props.mapViewer.activeWidget === this || this.props.download) &&
204
+ (this.props.mapViewer.activeWidget
205
+ ? !this.props.mapViewer.activeWidget.container.current.classList.contains(
206
+ 'info-container',
207
+ )
208
+ : true)
209
+ ) {
206
210
  this.props.view.hitTest(event).then((response) => {
207
211
  if (response.results.length > 0) {
208
212
  let graphic = response.results.filter((result) => {
@@ -0,0 +1,389 @@
1
+ import React, { createRef } from 'react';
2
+ import { loadModules } from 'esri-loader';
3
+ import Highcharts from 'highcharts';
4
+ import HighchartsReact from 'highcharts-react-official';
5
+ var GeometryEngine, Graphic;
6
+
7
+ class InfoWidget extends React.Component {
8
+ /**
9
+ * Creator of the InfoWidget widget class
10
+ * @param {*} props
11
+ */
12
+ constructor(props) {
13
+ super(props);
14
+ //We create a reference to a DOM element to be mounted
15
+ this.container = createRef();
16
+ //Initially, we set the state of the component to
17
+ //not be showing the basemap panel
18
+ this.state = { showMapMenu: false };
19
+ this.map = this.props.map;
20
+ this.menuClass =
21
+ 'esri-icon-description esri-widget--button esri-widget esri-interactive';
22
+ //this.chartOptions;
23
+ }
24
+
25
+ loader() {
26
+ return loadModules(['esri/geometry/geometryEngine', 'esri/Graphic']).then(
27
+ ([_GeometryEngine, _Graphic]) => {
28
+ [GeometryEngine, Graphic] = [_GeometryEngine, _Graphic];
29
+ },
30
+ );
31
+ }
32
+
33
+ /**
34
+ * Method that will be invoked when the
35
+ * button is clicked. It controls the open
36
+ * and close actions of the component
37
+ */
38
+ openMenu() {
39
+ if (this.state.showMapMenu) {
40
+ this.props.mapViewer.setActiveWidget();
41
+ this.container.current.querySelector('.info-panel').style.display =
42
+ 'none';
43
+ this.container.current
44
+ .querySelector('.esri-widget--button')
45
+ .classList.replace('esri-icon-right-arrow', 'esri-icon-description');
46
+ // By invoking the setState, we notify the state we want to reach
47
+ // and ensure that the component is rendered again
48
+ this.setState({ showMapMenu: false, pixelInfo: false });
49
+ this.props.view.popup.autoOpenEnabled = true;
50
+ this.removeMarker();
51
+ } else {
52
+ this.props.mapViewer.setActiveWidget(this);
53
+ this.container.current
54
+ .querySelector('.esri-widget--button')
55
+ .classList.replace('esri-icon-description', 'esri-icon-right-arrow');
56
+ this.container.current.querySelector('.info-panel').style.display =
57
+ 'block';
58
+ // By invoking the setState, we notify the state we want to reach
59
+ // and ensure that the component is rendered again
60
+ this.setState({ showMapMenu: true });
61
+ this.props.mapViewer.view.popup.close();
62
+ this.props.view.popup.autoOpenEnabled = false;
63
+ }
64
+ }
65
+ /**
66
+ * This method is executed after the rener method is executed
67
+ */
68
+ async componentDidMount() {
69
+ await this.loader();
70
+ this.props.view.ui.add(this.container.current, 'top-right');
71
+ this.props.view.on('click', (e) => {
72
+ if (this.props.mapViewer.activeWidget === this) {
73
+ let timeLayers = this.map.layers.items.filter(
74
+ (a) => a.timeInfo && a.visible,
75
+ );
76
+ let layer = timeLayers[timeLayers.length - 1];
77
+ let title;
78
+ if (layer.sublayers) {
79
+ title = layer.sublayers.items[0].title;
80
+ } else if (layer.activeLayer) {
81
+ title = layer.activeLayer.title;
82
+ } else {
83
+ title = layer.title;
84
+ }
85
+ this.identify(layer, e).then((response) => {
86
+ let variables = response.variables.options;
87
+ let variable = response.variables.selected;
88
+ if (
89
+ this.state.variables
90
+ ? variables.includes(this.state.variables.selected)
91
+ : false
92
+ ) {
93
+ variable = this.state.variables.selected;
94
+ }
95
+ let data = {
96
+ x: response.timeFields.values
97
+ .map((a) => {
98
+ return a[response.timeFields.start];
99
+ })
100
+ .sort((a, b) => {
101
+ return new Date(a).getTime() - new Date(b).getTime();
102
+ }),
103
+ y: response.data.values.map((a) => {
104
+ return Math.round(a[variable] * 100) / 100;
105
+ }),
106
+ };
107
+ let chartData = this.createChart(title, variable, data);
108
+ this.addMarker(e);
109
+ this.setState({
110
+ pixelInfo: true,
111
+ data: response,
112
+ options: chartData,
113
+ variables: { options: variables, selected: variable, title: title },
114
+ });
115
+ });
116
+ }
117
+ });
118
+ }
119
+
120
+ addMarker(evt) {
121
+ this.removeMarker();
122
+ let lat = evt.mapPoint.latitude;
123
+ let long = evt.mapPoint.longitude;
124
+ var point = {
125
+ type: 'point',
126
+ longitude: long,
127
+ latitude: lat,
128
+ };
129
+ var markerSymbol = {
130
+ type: 'simple-marker',
131
+ color: [255, 255, 255, 0.75],
132
+ outline: {
133
+ color: 'black',
134
+ width: '2px',
135
+ },
136
+ };
137
+ var pointGraphic = new Graphic({
138
+ geometry: point,
139
+ symbol: markerSymbol,
140
+ attributes: { long, lat, id: 'pixel-info' },
141
+ });
142
+ this.props.view.graphics.add(pointGraphic);
143
+ }
144
+
145
+ removeMarker() {
146
+ if (
147
+ this.props.view.graphics.items.find((a) => {
148
+ return a.attributes ? a.attributes.id === 'pixel-info' : false;
149
+ })
150
+ ) {
151
+ let marker = this.props.view.graphics.items.find((a) => {
152
+ return a.attributes && a.attributes.id === 'pixel-info';
153
+ });
154
+ this.props.view.graphics.remove(marker);
155
+ }
156
+
157
+ let marker = this.props.view.graphics.items.find((a) => {
158
+ return a.attributes ? a.attributes.id === 'pixel-info' : false;
159
+ });
160
+ this.props.view.graphics.remove(marker);
161
+ }
162
+
163
+ identify(layer, evt) {
164
+ let values = { timeFields: {}, data: {}, variables: {} };
165
+ //Complete time data
166
+ values.timeFields['start'] = layer.timeInfo.startField;
167
+ values.timeFields['end'] = layer.timeInfo.endField;
168
+ let timeQuery = layer.createQuery();
169
+ timeQuery.outFields = [layer.timeInfo.startField];
170
+ let fields = layer.fields
171
+ .filter((a) => {
172
+ return (
173
+ a.type !== 'date' &&
174
+ a.type !== 'geometry' &&
175
+ a.type !== 'string' &&
176
+ a.type !== 'oid'
177
+ );
178
+ })
179
+ .map((b) => {
180
+ return b.name;
181
+ });
182
+ let field = layer.displayField in fields ? layer.displayField : fields[0];
183
+ values.variables = { options: fields, selected: field };
184
+ if (layer.timeInfo.fullTimeExtent.endField) {
185
+ timeQuery.outFields.push(layer.timeInfo.endField);
186
+ }
187
+ timeQuery.returnDistinctValues = true;
188
+ timeQuery.returnGeometry = false;
189
+ let p1 = layer.queryFeatures(timeQuery).then((r) => {
190
+ let timevals = [];
191
+ r.features.forEach((e) => timevals.push(e.attributes));
192
+ values.timeFields['values'] = timevals;
193
+ });
194
+
195
+ //Query for data
196
+ let query = layer.createQuery();
197
+ query.geometry = this.props.view.toMap(evt); // the point location of the pointer
198
+ query.distance = 1000;
199
+ query.units = 'meters';
200
+ query.spatialRelationship = 'intersects'; // this is the default
201
+ query.returnGeometry = true;
202
+ query.outFields = [fields.toString()]; // Information to be returned
203
+ values.data['outFields'] = [fields.toString()];
204
+
205
+ let p2 = layer.queryFeatures(query).then((response) => {
206
+ //First of all, we get all the points with its distance to click point
207
+ let points = response.features.map((e) => {
208
+ return {
209
+ latitude: e.geometry.latitude,
210
+ longitude: e.geometry.longitude,
211
+ distance: GeometryEngine.distance(
212
+ this.props.view.toMap(evt),
213
+ e.geometry,
214
+ 'meters',
215
+ ),
216
+ };
217
+ });
218
+ let min_distance = Math.min.apply(
219
+ null,
220
+ points.map((e) => e.distance),
221
+ ); //minimum distance
222
+ let point = points.find((e) => e.distance <= min_distance); //minimum distance point
223
+ //get de info of the minimum distance point at all the times possible
224
+ let info = response.features.filter(
225
+ (e) =>
226
+ e.geometry.latitude === point.latitude &&
227
+ e.geometry.longitude === point.longitude,
228
+ );
229
+ values.data['values'] = info.map((e) => {
230
+ return e.attributes;
231
+ });
232
+ });
233
+
234
+ return Promise.all([p1, p2]).then(() => {
235
+ return values;
236
+ });
237
+ }
238
+
239
+ createChart(title, variable, chartData) {
240
+ let chartOptions = {
241
+ chart: {
242
+ height: 208,
243
+ },
244
+ title: {
245
+ text: title,
246
+ style: {},
247
+ },
248
+ subtitle: {
249
+ text: '',
250
+ style: {
251
+ display: 'none',
252
+ },
253
+ },
254
+ plotOptions: {
255
+ line: {
256
+ pointPlacement: 'between',
257
+ },
258
+ },
259
+ xAxis: {
260
+ title: {
261
+ text: null,
262
+ },
263
+ labels: {
264
+ format: '{value:%d/%m/%Y}',
265
+ },
266
+ type: 'datetime',
267
+ categories: chartData.x,
268
+ tickmarkPlacement: 'between',
269
+ startOnTick: true,
270
+ endOnTick: true,
271
+ },
272
+ legend: {
273
+ enabled: false,
274
+ },
275
+ tooltip: {
276
+ shared: true,
277
+ useHTML: true,
278
+ headerFormat: '{point.x:%d/%m/%Y}<br><b>{point.point.symbolName}</b>',
279
+ },
280
+ series: [
281
+ {
282
+ marker: {
283
+ enabled: false,
284
+ },
285
+ name: variable,
286
+ data: chartData.y,
287
+ shadow: false,
288
+ zIndex: 1,
289
+ color: '#a0b128',
290
+ },
291
+ ],
292
+ exporting: {
293
+ buttons: {
294
+ contextButton: {
295
+ enabled: false,
296
+ },
297
+ },
298
+ },
299
+ credits: {
300
+ enabled: false,
301
+ },
302
+ };
303
+ return chartOptions;
304
+ }
305
+
306
+ selectVariable(option) {
307
+ let variables = this.state.variables.options;
308
+ let variable = option;
309
+ let title = this.state.variables.title;
310
+ let data = {
311
+ x: this.state.data.timeFields.values
312
+ .map((a) => {
313
+ return a[this.state.data.timeFields.start];
314
+ })
315
+ .sort((a, b) => {
316
+ return new Date(a).getTime() - new Date(b).getTime();
317
+ }),
318
+ y: this.state.data.data.values.map((a) => {
319
+ return Math.round(a[variable] * 100) / 100;
320
+ }),
321
+ };
322
+ let chartData = this.createChart(title, variable, data);
323
+ this.setState({
324
+ options: chartData,
325
+ variables: { options: variables, selected: variable, title: title },
326
+ });
327
+ }
328
+
329
+ /**
330
+ * This method renders the component
331
+ * @returns jsx
332
+ */
333
+ render() {
334
+ let isEmpty;
335
+ if (this.state.data) {
336
+ isEmpty =
337
+ this.state.data.data.values.map((a) => {
338
+ return a[this.state.variables.selected];
339
+ }).length === 0;
340
+ }
341
+ return (
342
+ <>
343
+ <div ref={this.container} className="info-container">
344
+ <div
345
+ className={this.menuClass}
346
+ id="info_button"
347
+ title="Info"
348
+ onClick={this.openMenu.bind(this)}
349
+ onKeyDown={this.openMenu.bind(this)}
350
+ tabIndex="0"
351
+ role="button"
352
+ ></div>
353
+ <div className="info-panel">
354
+ {this.state.pixelInfo ? (
355
+ <>
356
+ <select
357
+ className="esri-select"
358
+ value={this.state.variables.selected}
359
+ onBlur={(e) => this.selectVariable(e.target.value)}
360
+ onChange={(e) => this.selectVariable(e.target.value)}
361
+ >
362
+ {this.state.variables.options.map((option) => (
363
+ <option key={option} value={option}>
364
+ {option}
365
+ </option>
366
+ ))}
367
+ </select>
368
+ {isEmpty ? (
369
+ <span className="info-panel-empty">No data</span>
370
+ ) : (
371
+ <HighchartsReact
372
+ highcharts={Highcharts}
373
+ options={this.state.options}
374
+ />
375
+ )}
376
+ </>
377
+ ) : (
378
+ <span className="info-panel-empty">
379
+ Click on the map to get pixel info
380
+ </span>
381
+ )}
382
+ </div>
383
+ </div>
384
+ </>
385
+ );
386
+ }
387
+ }
388
+
389
+ export default InfoWidget;
@@ -8,6 +8,7 @@ import { useIntl } from 'react-intl';
8
8
  import { Message, Modal } from 'semantic-ui-react';
9
9
  import AreaWidget from './AreaWidget';
10
10
  import TimesliderWidget from './TimesliderWidget';
11
+ import InfoWidget from './InfoWidget';
11
12
  var WMSLayer, WMTSLayer, FeatureLayer;
12
13
 
13
14
  export const AddCartItem = ({
@@ -27,7 +28,7 @@ export const AddCartItem = ({
27
28
 
28
29
  const checkArea = () => {
29
30
  let check = document.querySelector('.area-panel input:checked').value;
30
- let area = [];
31
+ let area = {};
31
32
  if (check === 'area') {
32
33
  let graphics = mapViewer.view.graphics;
33
34
  if (graphics.length === 0) {
@@ -76,6 +77,8 @@ export const AddCartItem = ({
76
77
  let datasetElem = document.querySelector('div[datasetid="' + id + '"]');
77
78
  let datasetData = {
78
79
  id: id,
80
+ UID: id,
81
+ unique_id: `${id}-${new Date().getTime()}`,
79
82
  area: area,
80
83
  };
81
84
  if (
@@ -711,6 +714,23 @@ class MenuWidget extends React.Component {
711
714
  if (nuts) {
712
715
  this.map.reorder(nuts, this.map.layers.items.length + 1);
713
716
  }
717
+ if (Object.keys(this.timeLayers).length > 0) {
718
+ if (!document.querySelector('.info-container')) {
719
+ let div = document.createElement('div');
720
+ document.querySelector('.esri-ui-top-right').appendChild(div);
721
+ ReactDOM.render(
722
+ <InfoWidget
723
+ view={this.props.view}
724
+ map={this.map}
725
+ mapViewer={this.props.mapViewer}
726
+ />,
727
+ div,
728
+ );
729
+ div.remove();
730
+ } else {
731
+ document.querySelector('.info-container').style.display = '';
732
+ }
733
+ }
714
734
  } else {
715
735
  let checkboxes = document.getElementsByName('layerCheckbox');
716
736
  let repeatedLayers = [];
@@ -727,6 +747,13 @@ class MenuWidget extends React.Component {
727
747
  }
728
748
  }
729
749
  this.updateCheckDataset(parentId);
750
+ if (
751
+ Object.keys(this.timeLayers).length === 0 &&
752
+ document.querySelector('.info-container')
753
+ ) {
754
+ this.props.mapViewer.closeActiveWidget();
755
+ document.querySelector('.info-container').style.display = 'none';
756
+ }
730
757
  this.setState({});
731
758
  }
732
759
 
@@ -19,10 +19,7 @@ class TimesliderWidget extends React.Component {
19
19
  }
20
20
 
21
21
  loader() {
22
- return loadModules([
23
- 'esri/widgets/TimeSlider',
24
- 'esri/layers/WMSLayer',
25
- ]).then(([_TimeSlider]) => {
22
+ return loadModules(['esri/widgets/TimeSlider']).then(([_TimeSlider]) => {
26
23
  [TimeSlider] = [_TimeSlider];
27
24
  });
28
25
  }
@@ -37,6 +37,11 @@
37
37
  max-height: 250px !important;
38
38
  }
39
39
 
40
+ .esri-ui-top-right,
41
+ .esri-ui-top-left {
42
+ z-index: 1;
43
+ }
44
+
40
45
  .esri-component.esri-zoom.esri-widget {
41
46
  background: none;
42
47
  box-shadow: none;
@@ -76,6 +81,10 @@
76
81
  order: 5;
77
82
  }
78
83
 
84
+ .info-container {
85
+ order: 6;
86
+ }
87
+
79
88
  /* Basemap */
80
89
  .basemap-container {
81
90
  display: flex;
@@ -586,6 +595,26 @@
586
595
  overflow: auto;
587
596
  }
588
597
 
598
+ /* Pixel info */
599
+ .info-container {
600
+ display: flex;
601
+ box-shadow: none !important;
602
+ }
603
+
604
+ .info-panel {
605
+ display: none;
606
+ overflow: auto;
607
+ width: 300px;
608
+ max-height: 240px;
609
+ padding: 1rem;
610
+ background-color: white;
611
+ font-size: 1rem;
612
+ }
613
+
614
+ .info-panel select {
615
+ margin-bottom: 1rem;
616
+ }
617
+
589
618
  /* Modal */
590
619
  .map-download-modal.ui.modal {
591
620
  margin: 0 !important;
@@ -75,6 +75,12 @@ class LegendWidget extends React.Component {
75
75
  break;
76
76
  }
77
77
  }
78
+
79
+ let legendRow = document.querySelectorAll('.esri-legend__layer-row');
80
+ for (let i = 0; i < legendRow.length; i++) {
81
+ legendRow[i].textContent.toLowerCase() === 'global' &&
82
+ legendRow[i].remove();
83
+ }
78
84
  // By invoking the setState, we notify the state we want to reach
79
85
  // and ensure that the component is rendered again
80
86
  this.mapViewer.setState({ showMapMenu: true });