@eeacms/volto-arcgis-block 0.1.264 → 0.1.266

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,6 +4,31 @@ 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.266](https://github.com/eea/volto-arcgis-block/compare/0.1.265...0.1.266) - 19 March 2024
8
+
9
+ #### :hammer_and_wrench: Others
10
+
11
+ - CLMS-2383-3050 (chore): added FAQ hyperlink [Unai Bolivar - [`445af32`](https://github.com/eea/volto-arcgis-block/commit/445af32d2a7c4326f3369657378bbe97b1cb4c2f)]
12
+ - CLMS-2383-3050 (chore): fixed lint error 2 [Unai Bolivar - [`93114ff`](https://github.com/eea/volto-arcgis-block/commit/93114ff28cc2ad1414a079e09489a0c732e8d8ff)]
13
+ - CLMS-2383-3050 (chore): fixed lint error [Unai Bolivar - [`0b53f2e`](https://github.com/eea/volto-arcgis-block/commit/0b53f2e4237aa111e667978c2f97459e1ba7c468)]
14
+ - CLMS-2383-3050 (bug): ready for push [Unai Bolivar - [`c452b23`](https://github.com/eea/volto-arcgis-block/commit/c452b234d7be3dff7dfd35cc14d18b8c0df2a215)]
15
+ - CLMS-2383-3050 (feat): implemented new functions for feature generation [Unai Bolivar - [`d31c2b9`](https://github.com/eea/volto-arcgis-block/commit/d31c2b94bb5232fd15631851549eedd6fa7e80be)]
16
+ - CLMS-2383-3050 (feat): implemented cleared rectangle handler when file is uploaded [Unai Bolivar - [`a90a1ff`](https://github.com/eea/volto-arcgis-block/commit/a90a1ff4121c0f4e12740865c1def8e1a8455a8c)]
17
+ - CLMS-2383-3050 (feat): Disabled legends for file uploads [Unai Bolivar - [`c5c3e16`](https://github.com/eea/volto-arcgis-block/commit/c5c3e163230952b1bf1b732b12c51256c07d5a14)]
18
+ - CLMS-2383-3050 (feat): Implemented nuts and countries layer removal when file uploaded and uploaded layer removal when countries or nuts is selected or a new file is uploaded and successfully processed [Unai Bolivar - [`3f47039`](https://github.com/eea/volto-arcgis-block/commit/3f47039d2deb4a0c9a7dc5016b415946d3bd6fb8)]
19
+ - CLMS-2383-3050 (feat): Implemented single polygon and geometry type checks for shp and geojson files [Unai Bolivar - [`06e43b2`](https://github.com/eea/volto-arcgis-block/commit/06e43b2a819124034d94407b2117a1ad6f6cd1fa)]
20
+ - CLMS-2383-3050 (feat): implemented extent bounding box limit, wkid checker, needs more testing. [Unai Bolivar - [`901d893`](https://github.com/eea/volto-arcgis-block/commit/901d8939226a25624e7708081c59283f5cd27bfa)]
21
+ - CLMS-2383-3050 (chore): implemented max file size limit and info button for file upload FAQ page [Unai Bolivar - [`a05d736`](https://github.com/eea/volto-arcgis-block/commit/a05d73609f84813e275351eee952c4dd318614d2)]
22
+ - CLMS-2383-3050 (chore): implemented simple error messages for file upload max size and incorrect format [Unai Bolivar - [`cbed86e`](https://github.com/eea/volto-arcgis-block/commit/cbed86e68dfe1e2fa61c75da743e1cf9a5332e8f)]
23
+ - CLMS-2383-3050 (chore): corrected button styles in upload form [Unai Bolivar - [`5ca6fb1`](https://github.com/eea/volto-arcgis-block/commit/5ca6fb1d946facf2543d034c23fa9806ac6bfa4c)]
24
+ - CLMS-2749 (feat): csv file upload and render on map is working [Unai Bolivar - [`b670eca`](https://github.com/eea/volto-arcgis-block/commit/b670eca70fc8569929d17b9dda9e585a768de1bd)]
25
+ - CLMS-2749 (feat): shape file upload and render on map is working [Unai Bolivar - [`b8a4731`](https://github.com/eea/volto-arcgis-block/commit/b8a473139f4eb5dceb9ec411c3c446570c630881)]
26
+ - CLMS-2749 (feat): geoJSON file upload and render on map is working [Unai Bolivar - [`a87f7f1`](https://github.com/eea/volto-arcgis-block/commit/a87f7f1c0028e9251ffb4e4e196739b48335c87f)]
27
+ - CLMS-2749 (feat): continuation [Unai Bolivar - [`a16254e`](https://github.com/eea/volto-arcgis-block/commit/a16254e661576630a97aed17df27ad346593a1af)]
28
+ - CLMS-2749 (feat): added new library for shp processing and most file operations have been built [Unai Bolivar - [`114902f`](https://github.com/eea/volto-arcgis-block/commit/114902f5636c18784979db8c6dc8fbf11ba86023)]
29
+ - CLMS-2749 (feat): Implementation of file upload in area selection for data viewer [Unai Bolivar - [`bce2e45`](https://github.com/eea/volto-arcgis-block/commit/bce2e458d5cf1cc9d6c39b4824f054343a2d9781)]
30
+ ### [0.1.265](https://github.com/eea/volto-arcgis-block/compare/0.1.264...0.1.265) - 13 March 2024
31
+
7
32
  ### [0.1.264](https://github.com/eea/volto-arcgis-block/compare/0.1.263...0.1.264) - 11 March 2024
8
33
 
9
34
  ### [0.1.263](https://github.com/eea/volto-arcgis-block/compare/0.1.262...0.1.263) - 29 February 2024
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-arcgis-block",
3
- "version": "0.1.264",
3
+ "version": "0.1.266",
4
4
  "description": "volto-arcgis-block: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: CodeSyntax",
@@ -70,9 +70,9 @@
70
70
  "devDependencies": {
71
71
  "@cypress/code-coverage": "^3.9.5",
72
72
  "babel-plugin-transform-class-properties": "^6.24.1",
73
+ "dotenv": "^16.3.2",
73
74
  "husky": "^8.0.3",
74
75
  "lint-staged": "13.1.4",
75
- "md5": "^2.3.0",
76
- "dotenv": "^16.3.2"
76
+ "md5": "^2.3.0"
77
77
  }
78
78
  }
@@ -4,9 +4,13 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4
4
 
5
5
  var Graphic,
6
6
  Extent,
7
+ CSVLayer,
7
8
  FeatureLayer,
9
+ Field,
8
10
  GroupLayer,
9
11
  Color,
12
+ //Query,
13
+ request,
10
14
  SimpleLineSymbol,
11
15
  SimpleFillSymbol;
12
16
 
@@ -35,41 +39,65 @@ class AreaWidget extends React.Component {
35
39
  this.nutsUrl = '';
36
40
  this.initFMI = this.initFMI.bind(this);
37
41
  this.mapviewer_config = this.props.mapviewer_config;
42
+ this.fileInput = createRef();
43
+ this.handleCsv = this.handleCsv.bind(this);
44
+ this.fileUploadLayer = null;
45
+ this.removeFileUploadedLayer = this.removeFileUploadedLayer.bind(this);
46
+ this.uploadPortal = this.props.urls.uploadPortal;
47
+ this.generateFeatureCollection = this.generateFeatureCollection.bind(this);
48
+ this.addFeatureCollectionToMap = this.addFeatureCollectionToMap.bind(this);
49
+ this.checkFeatureCount = this.checkFeatureCount.bind(this);
38
50
  }
39
51
 
40
52
  loader() {
41
53
  return loadModules([
42
54
  'esri/Graphic',
43
55
  'esri/geometry/Extent',
56
+ 'esri/layers/CSVLayer',
44
57
  'esri/layers/FeatureLayer',
58
+ 'esri/layers/support/Field',
45
59
  'esri/layers/GroupLayer',
46
60
  'esri/Color',
61
+ //'esri/rest/support/Query',
62
+ 'esri/request',
47
63
  'esri/symbols/SimpleLineSymbol',
48
64
  'esri/symbols/SimpleFillSymbol',
49
65
  ]).then(
50
66
  ([
51
67
  _Graphic,
52
68
  _Extent,
69
+ _CSVLayer,
53
70
  _FeatureLayer,
71
+ _Field,
54
72
  _GroupLayer,
55
73
  _Color,
74
+ //_Query,
75
+ _request,
56
76
  _SimpleLineSymbol,
57
77
  _SimpleFillSymbol,
58
78
  ]) => {
59
79
  [
60
80
  Graphic,
61
81
  Extent,
82
+ CSVLayer,
62
83
  FeatureLayer,
84
+ Field,
63
85
  GroupLayer,
64
86
  Color,
87
+ //Query,
88
+ request,
65
89
  SimpleLineSymbol,
66
90
  SimpleFillSymbol,
67
91
  ] = [
68
92
  _Graphic,
69
93
  _Extent,
94
+ _CSVLayer,
70
95
  _FeatureLayer,
96
+ _Field,
71
97
  _GroupLayer,
72
98
  _Color,
99
+ //_Query,
100
+ _request,
73
101
  _SimpleLineSymbol,
74
102
  _SimpleFillSymbol,
75
103
  ];
@@ -101,6 +129,7 @@ class AreaWidget extends React.Component {
101
129
  infoPopupType: 'area',
102
130
  });
103
131
  this.clearWidget();
132
+ this.removeFileUploadedLayer();
104
133
  this.container.current.querySelector(
105
134
  '#download_area_select_nuts0',
106
135
  ).checked = true;
@@ -183,6 +212,8 @@ class AreaWidget extends React.Component {
183
212
  //definitionExpression: 'LEVL_CODE=' + level,
184
213
  });
185
214
 
215
+ this.removeFileUploadedLayer();
216
+
186
217
  this.nutsGroupLayer.add(layer);
187
218
 
188
219
  let index = this.getHighestIndex();
@@ -204,6 +235,8 @@ class AreaWidget extends React.Component {
204
235
  popupEnabled: false,
205
236
  });
206
237
 
238
+ this.removeFileUploadedLayer();
239
+
207
240
  this.nutsGroupLayer.add(layer);
208
241
 
209
242
  let index = this.getHighestIndex();
@@ -211,6 +244,354 @@ class AreaWidget extends React.Component {
211
244
  this.props.map.reorder(this.nutsGroupLayer, index + 1);
212
245
  }
213
246
 
247
+ // FILE UPLOAD HANDLERS
248
+
249
+ // Trigger the file input click
250
+ handleUploadClick = (event) => {
251
+ event.preventDefault();
252
+ this.fileInput.current.click();
253
+ };
254
+
255
+ handleFileUpload = (e) => {
256
+ //Store the file as Blob
257
+ let fileBlob = e.target.files[0];
258
+ //Get the file name
259
+ const fileName = e.target.value.toLowerCase();
260
+ //console.log('file name: ', fileName);
261
+
262
+ //Get the file from the form
263
+ const file = document.getElementById('uploadForm');
264
+ //console.log('uploaded file from form: ', file);
265
+
266
+ //List allowed file extensions
267
+ let fileExtensions = ['zip', 'geojson', 'csv'];
268
+
269
+ // Get the file extension
270
+ let fileExtension = fileName.split('.').pop();
271
+
272
+ //console.log('file extension: ', fileExtension);
273
+
274
+ //Check if the file format is not supported
275
+ if (fileExtensions.indexOf(fileExtension) === -1) {
276
+ this.setState({
277
+ showInfoPopup: true,
278
+ infoPopupType: 'fileFormat',
279
+ });
280
+ return;
281
+ }
282
+
283
+ //Check if the file size is over the 10mb file size limit
284
+ if (
285
+ (file.size > 2000000 && fileExtension === 'zip') ||
286
+ (file.size > 1000000 && fileExtension === 'geojson') ||
287
+ (file.size > 1000000 && fileExtension === 'csv')
288
+ ) {
289
+ this.setState({
290
+ showInfoPopup: true,
291
+ infoPopupType: 'fileLimit',
292
+ });
293
+ return;
294
+ }
295
+
296
+ //Read the file
297
+ let reader = new FileReader();
298
+
299
+ reader.onload = (event) => {
300
+ switch (fileExtension) {
301
+ // case 'zip':
302
+ // this.handleShp(event.target.result);
303
+ // break;
304
+ // case 'geojson':
305
+ // let parsedData;
306
+ // try {
307
+ // parsedData = JSON.parse(event.target.result);
308
+ // } catch (e) {
309
+ // console.error('Failed to parse JSON', e);
310
+ // return;
311
+ // }
312
+ // this.handleGeoJson(parsedData);
313
+ // break;
314
+ case 'csv':
315
+ this.handleCsv(event.target.result);
316
+ break;
317
+ default:
318
+ break;
319
+ }
320
+ };
321
+
322
+ switch (fileExtension) {
323
+ case 'zip':
324
+ this.generateFeatureCollection(fileName, file, 'shapefile');
325
+ //reader.readAsArrayBuffer(file);
326
+ break;
327
+ case 'geojson':
328
+ this.generateFeatureCollection(fileName, file, 'geojson');
329
+ //reader.readAsText(file);
330
+ break;
331
+ case 'csv':
332
+ //this.generateFeatureCollection(
333
+ // fileName,
334
+ // file,
335
+ // 'csv',
336
+ //);
337
+ reader.readAsText(fileBlob);
338
+ break;
339
+ default:
340
+ break;
341
+ }
342
+ };
343
+
344
+ generateFeatureCollection(fileName, file, inputFormat) {
345
+ let name = fileName.split('.');
346
+
347
+ // Chrome adds c:\fakepath to the value - we need to remove it
348
+ name = name[0].replace('c:\\fakepath\\', '');
349
+
350
+ //console.log('from generateFeatureCollection() name: ', name);
351
+
352
+ // define the input params for generate see the rest doc for details
353
+ // https://developers.arcgis.com/rest/users-groups-and-items/generate.htm
354
+ const params = {
355
+ name: name,
356
+ targetSR: this.props.view.spatialReference,
357
+ maxRecordCount: 1000,
358
+ enforceInputFileSizeLimit: true,
359
+ enforceOutputJsonSizeLimit: true,
360
+ };
361
+ // generalize features to 10 meters for better performance
362
+ params.generalize = true;
363
+ params.maxAllowableOffset = 10;
364
+ params.reducePrecision = true;
365
+ params.numberOfDigitsAfterDecimal = 0;
366
+
367
+ const myContent = {
368
+ filetype: inputFormat,
369
+ publishParameters: JSON.stringify(params),
370
+ f: 'json',
371
+ };
372
+ // use the REST generate operation to generate a feature collection from the zipped shapefile
373
+ request(this.uploadPortal + '/sharing/rest/content/features/generate', {
374
+ query: myContent,
375
+ body: file,
376
+ responseType: 'json',
377
+ })
378
+ .then((response) => {
379
+ if (response.data && response.data.featureCollection) {
380
+ //console.log('response data: ', response.data);
381
+ //Check for more than a single feature
382
+ if (this.checkFeatureCount(response.data.featureCollection) === false)
383
+ return;
384
+ //Create a feature layer from the feature collection
385
+ this.addFeatureCollectionToMap(response.data.featureCollection);
386
+ } else {
387
+ //console.error('Unexpected response structure:', response);
388
+ }
389
+ })
390
+ .catch((error) => {
391
+ //console.error('From generateFeatureCollection function', error);
392
+ });
393
+ }
394
+
395
+ // add the feature collection to the map and zoom to the feature collection extent
396
+ // if you want to persist the feature collection when you reload browser, you could store the
397
+ // collection in local storage by serializing the layer using featureLayer.toJson()
398
+ // see the 'Feature Collection in Local Storage' sample for an example of how to work with local storage
399
+ addFeatureCollectionToMap(featureCollection) {
400
+ let sourceGraphics = [];
401
+ const layers = featureCollection.layers.map((layer) => {
402
+ const graphics = layer.featureSet.features.map((feature) => {
403
+ return Graphic.fromJSON(feature);
404
+ });
405
+ sourceGraphics = sourceGraphics.concat(graphics);
406
+ const featureLayer = new FeatureLayer({
407
+ objectIdField: 'FID',
408
+ source: graphics,
409
+ legendEnabled: false,
410
+ title: 'uploadLayer',
411
+ fields: layer.layerDefinition.fields.map((field) => {
412
+ return Field.fromJSON(field);
413
+ }),
414
+ });
415
+ return featureLayer;
416
+ });
417
+
418
+ //Check for the correct spatial reference
419
+ //console.log("layer: ", layers);
420
+ if (this.checkWkid(layers[0]?.spatialReference) === false) return;
421
+
422
+ let geometry = new Extent(
423
+ featureCollection.layers[0].layerDefinition.extent,
424
+ );
425
+
426
+ //If checkExtent returns false, add the layer to the map
427
+ if (this.checkExtent(geometry.extent)) {
428
+ this.setState({
429
+ showInfoPopup: true,
430
+ infoPopupType: 'fullDataset',
431
+ });
432
+ } else {
433
+ //Remove old uploaded file and save new one to component props for reference
434
+ this.removeFileUploadedLayer();
435
+ this.fileUploadLayer = { layers: layers, sourceGraphics: sourceGraphics };
436
+ //remove NUTS and COUNTRIES layers from map
437
+ this.removeNutsLayers();
438
+
439
+ //Add uploaded layer to the map and zoom to the extent
440
+ this.props.map.addMany(layers);
441
+ this.props.view.goTo(sourceGraphics).catch((error) => {
442
+ //console.error('From addFeatureCollectionToMap function', error);
443
+ });
444
+ //Send the area to the parent component
445
+ this.props.updateArea({
446
+ origin: { x: geometry.extent.xmin, y: geometry.extent.ymin },
447
+ end: { x: geometry.extent.xmax, y: geometry.extent.ymax },
448
+ });
449
+ //Order the layer in the map
450
+ let index = this.getHighestIndex();
451
+ this.props.map.reorder(this.fileUploadLayer, index + 1);
452
+ this.setState({
453
+ showInfoPopup: true,
454
+ infoPopupType: 'download',
455
+ });
456
+ }
457
+ }
458
+
459
+ //check if the featurecollection has more than one feature
460
+
461
+ checkFeatureCount(layers) {
462
+ //debugger;
463
+ if (layers.layers[0].featureSet.features.length > 1) {
464
+ this.setState({
465
+ showInfoPopup: true,
466
+ infoPopupType: 'singleFeature',
467
+ });
468
+ return false;
469
+ } else {
470
+ return true;
471
+ }
472
+ }
473
+
474
+ //Check if the file has a polygon geometry type
475
+
476
+ /* uncomment the code below before pushing to DEMO */
477
+
478
+ //if (data?.features?.geometry?.type !== 'polygon') {
479
+ // this.setState({
480
+ // showInfoPopup: true,
481
+ // infoPopupType: 'singlePolygon',
482
+ // });
483
+ // return;
484
+ //}
485
+
486
+ //Display CSV on the map
487
+
488
+ handleCsv(data) {
489
+ //Create a CSV layer
490
+ const blob = new Blob([data], {
491
+ type: 'plain/text',
492
+ });
493
+
494
+ let url = URL.createObjectURL(blob);
495
+
496
+ const csvLayer = new CSVLayer({
497
+ url,
498
+ legendEnabled: false,
499
+ title: 'uploadLayer',
500
+ });
501
+
502
+ //debugger;
503
+ //Query all features insisde the CSV layer
504
+
505
+ //csvLayer.load().then(function(){
506
+ // let query = new Query({
507
+ // where: "mag > 5",
508
+ // returnGeometry: true
509
+ // });
510
+ //
511
+ // return csvLayer.queryFeatures(query);
512
+ //})
513
+ //.then(function(results){
514
+ // console.log(results);
515
+ //})
516
+ //.catch(function (error) {
517
+ // console.error("From CSV query: ", error);
518
+ //});
519
+ //Check if the file has the correct spatial reference
520
+ if (this.checkWkid(csvLayer?.spatialReference) === false) return;
521
+
522
+ //Check if the file extent is larger than the limit
523
+ //let geometry = new Extent({
524
+ // xmin: data?.features[0]?.geometry.bbox[0],
525
+ // xmax: data?.features[0]?.geometry.bbox[1],
526
+ // ymin: data?.features[0]?.geometry.bbox[2],
527
+ // ymax: data?.features[0]?.geometry.bbox[3],
528
+ // spatialReference: { wkid: 4326 },
529
+ //});
530
+
531
+ //If checkExtent returns false, add the layer to the map
532
+ //if (this.checkExtent(geometry)) {
533
+ // this.setState({
534
+ // showInfoPopup: true,
535
+ // infoPopupType: 'fullDataset',
536
+ // });
537
+ //} else {
538
+ this.removeFileUploadedLayer();
539
+ this.fileUploadLayer = csvLayer;
540
+ this.removeNutsLayers();
541
+ this.props.map.add(this.fileUploadLayer);
542
+ this.setState({
543
+ showInfoPopup: true,
544
+ infoPopupType: 'download',
545
+ });
546
+ //}
547
+ }
548
+
549
+ checkWkid(spatialReference) {
550
+ if (
551
+ spatialReference &&
552
+ spatialReference?.isWGS84 &&
553
+ spatialReference?.wkid === 4326
554
+ ) {
555
+ return true;
556
+ } else {
557
+ this.setState({
558
+ showInfoPopup: true,
559
+ infoPopupType: 'incorrectWkid',
560
+ });
561
+ }
562
+ }
563
+
564
+ //Remove the NUTS layers from the map
565
+
566
+ removeNutsLayers() {
567
+ //find all the radio buttons
568
+ let radioButtons = document.querySelectorAll('fieldset.ccl-fieldset');
569
+
570
+ // Isolate the the checked radio button
571
+ let selectedRadioButton = Array.from(radioButtons).find(
572
+ (radioButton) => radioButton.querySelector('input').checked,
573
+ );
574
+
575
+ //Uncheck the selected radio button
576
+ if (selectedRadioButton) {
577
+ selectedRadioButton.querySelector('input').checked = false;
578
+ }
579
+
580
+ //Remove the layers in this.nutsGroupLayer from the map
581
+ //this.nutsGroupLayer.removeAll();
582
+ this.clearWidget();
583
+ }
584
+
585
+ //Remove the uploaded layer from the map
586
+
587
+ removeFileUploadedLayer() {
588
+ if (this.fileUploadLayer !== null) {
589
+ this.props.map.removeMany(this.fileUploadLayer.layers);
590
+ //this.props.view.graphics.removeMany(this.fileUploadLayer.sourceGraphics);
591
+ this.fileUploadLayer = null;
592
+ }
593
+ }
594
+
214
595
  getHighestIndex() {
215
596
  let index = 0;
216
597
  document.querySelectorAll('.active-layer').forEach((layer) => {
@@ -396,19 +777,15 @@ class AreaWidget extends React.Component {
396
777
  )
397
778
  ) {
398
779
  layer = result.graphic;
780
+ return layer;
781
+ } else {
782
+ return false;
399
783
  }
400
- return layer;
401
784
  })[0].graphic;
402
785
  if (graphic) {
403
786
  let geometry = graphic.geometry;
404
787
  if (geometry.type === 'polygon') {
405
- let nuts;
406
- if ('countries'.includes(graphic.layer.id)) {
407
- nuts = graphic.attributes.ISO_2DIGIT;
408
- } else {
409
- nuts = graphic.attributes.NUTS_ID;
410
- }
411
- this.props.updateArea(nuts);
788
+ this.props.updateArea(graphic);
412
789
  let symbol = new SimpleFillSymbol(
413
790
  'solid',
414
791
  new SimpleLineSymbol('solid', new Color([232, 104, 80]), 2),
@@ -432,6 +809,9 @@ class AreaWidget extends React.Component {
432
809
  });
433
810
  }
434
811
  });
812
+ //this.props.view.watch('updating', () => {
813
+ // console.log('graphics: ', this.props.view.graphics);
814
+ //});
435
815
 
436
816
  this.props.download
437
817
  ? this.container !== null && this.props.view.ui.add(this.container)
@@ -648,6 +1028,51 @@ class AreaWidget extends React.Component {
648
1028
  </div>
649
1029
  </fieldset>
650
1030
  </div>
1031
+ <br />
1032
+ <div className="area-header">
1033
+ Upload a file with your area of interest
1034
+ <a
1035
+ href="https://land.copernicus.eu/en/faq/map-viewer/how-can-i-upload-a-file-with-my-area-of-interest"
1036
+ target="_blank"
1037
+ rel="noopener noreferrer"
1038
+ onClick={(e) => e.stopPropagation()}
1039
+ onKeyDown={(e) => e.stopPropagation()}
1040
+ >
1041
+ <span className="map-menu-icon nuts-menu-icon">
1042
+ <FontAwesomeIcon icon={['fa', 'info-circle']} />
1043
+ </span>
1044
+ </a>
1045
+ </div>
1046
+ <div className="ccl-form">
1047
+ <form
1048
+ enctype="multipart/form-data"
1049
+ method="post"
1050
+ id="uploadForm"
1051
+ >
1052
+ <div className="field">
1053
+ <label className="file-upload">
1054
+ <span>
1055
+ File formats supported: shp(zip), geojson, csv
1056
+ </span>
1057
+ <input
1058
+ type="file"
1059
+ name="file"
1060
+ id="inFile"
1061
+ ref={this.fileInput}
1062
+ style={{ display: 'none' }}
1063
+ onChange={this.handleFileUpload}
1064
+ />
1065
+ </label>
1066
+ </div>
1067
+ </form>
1068
+ <button
1069
+ className="esri-button"
1070
+ onClick={this.handleUploadClick}
1071
+ type="submit"
1072
+ >
1073
+ Upload File
1074
+ </button>
1075
+ </div>
651
1076
  </div>
652
1077
  </div>
653
1078
  </div>
@@ -697,6 +1122,57 @@ class AreaWidget extends React.Component {
697
1122
  </div>
698
1123
  </>
699
1124
  )}
1125
+ {this.state.infoPopupType === 'fileFormat' && (
1126
+ <>
1127
+ <span className="drawRectanglePopup-icon">
1128
+ <FontAwesomeIcon icon={['fas', 'info-circle']} />
1129
+ </span>
1130
+ <div className="drawRectanglePopup-text">
1131
+ The file format is not correct.
1132
+ </div>
1133
+ </>
1134
+ )}
1135
+ {this.state.infoPopupType === 'fileLimit' && (
1136
+ <>
1137
+ <span className="drawRectanglePopup-icon">
1138
+ <FontAwesomeIcon icon={['fas', 'info-circle']} />
1139
+ </span>
1140
+ <div className="drawRectanglePopup-text">
1141
+ Uploading files larger than 10MB is not allowed.
1142
+ </div>
1143
+ </>
1144
+ )}
1145
+ {this.state.infoPopupType === 'incorrectWkid' && (
1146
+ <>
1147
+ <span className="drawRectanglePopup-icon">
1148
+ <FontAwesomeIcon icon={['fas', 'info-circle']} />
1149
+ </span>
1150
+ <div className="drawRectanglePopup-text">
1151
+ The spatial reference is not correct.
1152
+ </div>
1153
+ </>
1154
+ )}
1155
+ {this.state.infoPopupType === 'singleFeature' && (
1156
+ <>
1157
+ <span className="drawRectanglePopup-icon">
1158
+ <FontAwesomeIcon icon={['fas', 'info-circle']} />
1159
+ </span>
1160
+ <div className="drawRectanglePopup-text">
1161
+ Uploading files with more than a single feature is not
1162
+ allowed.
1163
+ </div>
1164
+ </>
1165
+ )}
1166
+ {this.state.infoPopupType === 'singlePolygon' && (
1167
+ <>
1168
+ <span className="drawRectanglePopup-icon">
1169
+ <FontAwesomeIcon icon={['fas', 'info-circle']} />
1170
+ </span>
1171
+ <div className="drawRectanglePopup-text">
1172
+ Uploaded file is not a polygon geometry type.
1173
+ </div>
1174
+ </>
1175
+ )}
700
1176
  </div>
701
1177
  </div>
702
1178
  </div>
@@ -346,7 +346,7 @@ class MapViewer extends React.Component {
346
346
  renderArea() {
347
347
  if (this.props.mapviewer_config.Download) return;
348
348
  if (this.view) {
349
- return <CheckLogin reference={this} />;
349
+ return <CheckLogin reference={this} urls={this.cfgUrls} />;
350
350
  }
351
351
  }
352
352
 
@@ -26,6 +26,7 @@ export const AddCartItem = ({
26
26
  download,
27
27
  areaData,
28
28
  dataset,
29
+ handleOpenPopup,
29
30
  }) => {
30
31
  const { addCartItem, isLoggedIn } = useCartState();
31
32
 
@@ -48,7 +49,13 @@ export const AddCartItem = ({
48
49
  } else {
49
50
  if (areaData) {
50
51
  area.type = 'nuts';
51
- area.value = areaData;
52
+ if (areaData.geometry.type === 'polygon') {
53
+ if ('countries'.includes(areaData.layer.id)) {
54
+ area.value = areaData.attributes.ISO_2DIGIT;
55
+ } else {
56
+ area.value = areaData.attributes.NUTS_ID;
57
+ }
58
+ }
52
59
  } else {
53
60
  area = '';
54
61
  }
@@ -67,6 +74,65 @@ export const AddCartItem = ({
67
74
  }
68
75
  });
69
76
  };
77
+ const checkExtent = (e) => {
78
+ let intersection = false;
79
+ let areaExtent = null;
80
+ let check = document.querySelector('.area-panel input:checked').value;
81
+ if (check === 'area') {
82
+ areaExtent = new Extent({
83
+ xmin: Math.min(areaData.end.x, areaData.origin.x),
84
+ xmax: Math.max(areaData.end.x, areaData.origin.x),
85
+ ymin: Math.min(areaData.end.y, areaData.origin.y),
86
+ ymax: Math.max(areaData.end.y, areaData.origin.y),
87
+ });
88
+ } else {
89
+ areaExtent = areaData.geometry;
90
+ }
91
+ if (dataset?.DatasetTitle) {
92
+ Object.keys(props.layers).forEach((id) => {
93
+ if (
94
+ props.layers[id]?.DatasetTitle &&
95
+ dataset.DatasetTitle === props.layers[id].DatasetTitle
96
+ ) {
97
+ let layerExtent = null;
98
+ if (props.layers[id].fullExtent) {
99
+ layerExtent = new Extent({
100
+ xmin: props.layers[id].fullExtent.xmin,
101
+ ymin: props.layers[id].fullExtent.ymin,
102
+ xmax: props.layers[id].fullExtent.xmax,
103
+ ymax: props.layers[id].fullExtent.ymax,
104
+ });
105
+ } else if (
106
+ props.layers[id].fullExtents &&
107
+ props.layers[id].fullExtents[0]
108
+ ) {
109
+ layerExtent = new Extent({
110
+ xmin: props.layers[id].fullExtents[0].xmin,
111
+ ymin: props.layers[id].fullExtents[0].ymin,
112
+ xmax: props.layers[id].fullExtents[0].xmax,
113
+ ymax: props.layers[id].fullExtents[0].ymax,
114
+ });
115
+ } else {
116
+ layerExtent = new Extent({
117
+ xmin: -20037508.342789,
118
+ ymin: -20037508.342789,
119
+ xmax: 20037508.342789,
120
+ ymax: 20037508.342789,
121
+ });
122
+ }
123
+ if (layerExtent.intersects(areaExtent)) {
124
+ intersection = true;
125
+ }
126
+ }
127
+ });
128
+ if (intersection) {
129
+ checkArea();
130
+ } else {
131
+ e.currentTarget.appendChild(document.querySelector('.popup-container'));
132
+ handleOpenPopup();
133
+ }
134
+ }
135
+ };
70
136
 
71
137
  const checkCartData = (cartData, area, dataset) => {
72
138
  if (!dataset) {
@@ -112,7 +178,7 @@ export const AddCartItem = ({
112
178
  document.querySelector('.drawRectanglePopup-block').style
113
179
  .display === 'none'
114
180
  ) {
115
- checkArea(e);
181
+ checkExtent(e);
116
182
  }
117
183
  }
118
184
  }}
@@ -144,7 +210,7 @@ export const AddCartItem = ({
144
210
  document.querySelector('#map_area_button').click();
145
211
  }
146
212
  } else {
147
- checkArea(e);
213
+ checkExtent(e);
148
214
  }
149
215
  }
150
216
  }}
@@ -254,6 +320,7 @@ class MenuWidget extends React.Component {
254
320
  setNoServiceModal: true,
255
321
  TMSLayerObj: null,
256
322
  draggedElements: [],
323
+ popup: false,
257
324
  };
258
325
  this.menuClass =
259
326
  'esri-icon-drag-horizontal esri-widget--button esri-widget esri-interactive';
@@ -269,6 +336,7 @@ class MenuWidget extends React.Component {
269
336
  this.prepareHotspotLayers = this.prepareHotspotLayers.bind(this);
270
337
  this.activeLayersToHotspotData = this.activeLayersToHotspotData.bind(this);
271
338
  this.getLimitScale = this.getLimitScale.bind(this);
339
+ this.handleOpenPopup = this.handleOpenPopup.bind(this);
272
340
  // add zoomend listener to map to show/hide zoom in message
273
341
  this.view.watch('stationary', (isStationary) => {
274
342
  let snowAndIceInSessionStorage = sessionStorage.getItem('snowAndIce');
@@ -1403,6 +1471,7 @@ class MenuWidget extends React.Component {
1403
1471
  download={this.props.download}
1404
1472
  areaData={this.props.area}
1405
1473
  dataset={dataset}
1474
+ handleOpenPopup={this.handleOpenPopup}
1406
1475
  />
1407
1476
  ) : (
1408
1477
  <span
@@ -3062,16 +3131,6 @@ class MenuWidget extends React.Component {
3062
3131
  }
3063
3132
  });
3064
3133
  if (layers.length === 0 && document.querySelector('.info-container')) {
3065
- if (
3066
- this.props.view.graphics.items.find((a) => {
3067
- return a.attributes ? a.attributes.id === 'pixel-info' : false;
3068
- })
3069
- ) {
3070
- let marker = this.props.view.graphics.items.find((a) => {
3071
- return a.attributes && a.attributes.id === 'pixel-info';
3072
- });
3073
- this.props.view.graphics.remove(marker);
3074
- }
3075
3134
  if (
3076
3135
  this.props.mapViewer.activeWidget?.container.current.className ===
3077
3136
  'info-container esri-component'
@@ -3758,6 +3817,23 @@ class MenuWidget extends React.Component {
3758
3817
  }, 100);
3759
3818
  }
3760
3819
  }
3820
+ handleOpenPopup = () => {
3821
+ clearTimeout(this.timeout);
3822
+ this.setState({
3823
+ popup: true,
3824
+ });
3825
+
3826
+ this.timeout = setTimeout(() => {
3827
+ this.handleClosePopup();
3828
+ }, 2000);
3829
+ };
3830
+
3831
+ handleClosePopup = () => {
3832
+ this.setState({
3833
+ popup: false,
3834
+ });
3835
+ clearTimeout(this.timeout);
3836
+ };
3761
3837
 
3762
3838
  /**
3763
3839
  * This method renders the component
@@ -3768,6 +3844,15 @@ class MenuWidget extends React.Component {
3768
3844
  <>
3769
3845
  <div ref={this.container} className="map-left-menu-container">
3770
3846
  <div className="map-menu tab-container" id="tabcontainer">
3847
+ <Popup
3848
+ type={'info'}
3849
+ open={this.state.popup}
3850
+ position="right center"
3851
+ trigger={<div className="popup-container"></div>}
3852
+ offset={[0, 20]}
3853
+ >
3854
+ {'No data available on the selected area'}
3855
+ </Popup>
3771
3856
  <div className="tabs" role="tablist">
3772
3857
  <span
3773
3858
  className={!this.props.download ? 'tab tab-selected' : 'tab'}
@@ -3874,6 +3959,7 @@ class MenuWidget extends React.Component {
3874
3959
  mapViewer={this.props.mapViewer}
3875
3960
  download={this.props.download}
3876
3961
  areaData={this.props.area}
3962
+ handleOpenPopup={this.handleOpenPopup}
3877
3963
  />
3878
3964
  </div>
3879
3965
  )}
@@ -246,6 +246,7 @@ const config = {
246
246
  'https://gisco-services.ec.europa.eu/maps/wmts/CountriesWorld/EPSG3857/0/0/0.png',
247
247
  countriesWorldTemplate:
248
248
  'https://gisco-services.ec.europa.eu/maps/tiles/CountriesWorld/EPSG3857/{z}/{x}/{y}.png',
249
+ uploadPortal: 'https://www.arcgis.com',
249
250
  },
250
251
  };
251
252
  export default config;
@@ -352,6 +352,21 @@ div.esri-popover
352
352
  label.ccl-form-radio-label span.nuts-menu-icon {
353
353
  margin-left: 9rem;
354
354
  }
355
+
356
+ .area-container .esri-button {
357
+ border-color: #a0b128;
358
+ margin-left: 0.25rem;
359
+ background-color: #a0b128;
360
+ color: white !important;
361
+ transition: all 0.3s ease-out;
362
+ }
363
+
364
+ .area-container .esri-button:hover {
365
+ border-color: #a0b128;
366
+ background-color: white;
367
+ color: #a0b128 !important;
368
+ }
369
+
355
370
  /* Left menu */
356
371
  .map-menu {
357
372
  position: relative;
@@ -1367,7 +1382,7 @@ input[type='range']::-ms-track {
1367
1382
  display: flex;
1368
1383
  width: 19rem;
1369
1384
  height: 60px;
1370
- justify-content: space-between;
1385
+ justify-content: space-around;
1371
1386
  padding: 0.6rem 0.8rem 0.8rem 0.8rem;
1372
1387
  background-color: white;
1373
1388
  }
@@ -1381,6 +1396,7 @@ input[type='range']::-ms-track {
1381
1396
  }
1382
1397
 
1383
1398
  .drawRectanglePopup-text {
1399
+ flex: auto;
1384
1400
  font-family: 'Lato', sans-serif;
1385
1401
  font-size: 0.875rem;
1386
1402
  }