Преглед изворни кода

Merge branch 'develop' of jmacura/polirural-attractiveness-clustering into master

jmacura пре 5 година
родитељ
комит
0f6dda99b6

Разлика између датотеке није приказан због своје велике величине
+ 379 - 233
package-lock.json


+ 7 - 7
package.json

@@ -25,22 +25,22 @@
   "license": "MIT",
   "dependencies": {
     "csvtojson": "^2.0.10",
-    "hslayers-ng": "^1.24.0",
+    "hslayers-ng": "^1.24.2",
     "hsv2rgb": "^1.1.0",
     "http-server": "^0.12.3"
   },
   "devDependencies": {
-    "@babel/core": "^7.10.2",
+    "@babel/core": "^7.11.1",
     "@babel/plugin-syntax-dynamic-import": "^7.8.3",
     "babel-loader": "^8.1.0",
     "babel-plugin-angularjs-annotate": "^0.10.0",
     "clean-webpack-plugin": "^3.0.0",
-    "css-loader": "^3.5.3",
-    "eslint": "^7.1.0",
+    "css-loader": "^3.6.0",
+    "eslint": "^7.6.0",
     "eslint-config-angular": "^0.5.0",
     "eslint-config-openlayers": "^14.0.0",
     "eslint-plugin-angular": "^4.0.1",
-    "eslint-plugin-import": "^2.20.2",
+    "eslint-plugin-import": "^2.22.0",
     "eslint-plugin-node": "^11.1.0",
     "eslint-plugin-promise": "^4.2.1",
     "extract-loader": "^5.1.0",
@@ -55,8 +55,8 @@
     "postcss-prefix-selector": "^1.7.1",
     "style-loader": "^1.2.1",
     "url-loader": "^4.1.0",
-    "webpack": "^4.43.0",
-    "webpack-cli": "^3.3.11",
+    "webpack": "^4.44.1",
+    "webpack-cli": "^3.3.12",
     "webpack-dev-server": "^3.11.0",
     "webpack-merge": "^4.2.2"
   }

+ 25 - 6
src/adjuster/adjuster.component.js

@@ -2,20 +2,39 @@ export const AdjusterComponent = {
   template: require('./adjuster.directive.html'),
   controller: function (
     $scope,
-    HsCore,
-    HsConfig,
+    //HsCore,
+    //HsConfig,
     PraAdjusterService,
     HsUtilsService
   ) {
     'ngInject';
-    $scope.loading = false;
-    $scope.HsUtilsService = HsUtilsService;
+    //$scope.loading = false;
+    //$scope.HsUtilsService = HsUtilsService;
+    $scope.descriptionVisible = false;
 
     angular.extend($scope, {
-      HsCore,
+      //HsCore,
       PraAdjusterService,
+      noDataSelected,
     });
 
-    $scope.$emit('scope_loaded', 'adjuster');
+    //$scope.$emit('scope_loaded', 'adjuster');
+
+    function noDataSelected() {
+      if (PraAdjusterService.factors.length === 0) {
+        return true;
+      }
+      let datasetsEffectivelyTurnedOn = [];
+      for (const factor of PraAdjusterService.factors) {
+        if (factor.weight === 0) {
+          continue;
+        }
+        datasetsEffectivelyTurnedOn = [
+          ...datasetsEffectivelyTurnedOn,
+          ...factor.datasets.filter((ds) => ds.included),
+        ];
+      }
+      return datasetsEffectivelyTurnedOn.length === 0;
+    }
   },
 };

+ 26 - 18
src/adjuster/adjuster.directive.html

@@ -1,25 +1,33 @@
 <div class="card card-default mainpanel">
   <hs.layout.panel-header panel-name="adjuster" panel-title="'Adjust factors'||translate"></hs.layout.panel-header>
   <div class="card-body">
-    <button type="button" class="btn btn-primary" ng-click="PraAdjusterService.apply()" ng-disabled="PraAdjusterService.isClusteringInProcess()">Calculate clusters</button>
-    <div class="card-body">
-      <div ng-repeat="factor in PraAdjusterService.factors">
-        <div class="d-flex flex-row">
-          <div class="p-2 flex-grow-1">
-            <span class="glyphicon cursor-pointer" ng-class="datasetlistVisible ? 'icon-chevron-down' : 'icon-chevron-right'"
-              ng-click="datasetlistVisible = !datasetlistVisible"></span>
-            <label class="cursor-pointer" ng-click="datasetlistVisible = !datasetlistVisible">{{factor.name}}</label>
-          </div>
-          <div class="p-2">{{(factor.weight * 100).toFixed(0)}}&nbsp;%</div>
+    <div class="p-2 center-block">
+      <button type="button" class="btn btn-primary" ng-click="PraAdjusterService.apply()"
+        ng-disabled="PraAdjusterService.isClusteringInProcess() || noDataSelected()">Calculate clusters</button>
+      <div class="text-warning pt-2" ng-show="noDataSelected()">Select at least one dataset and set at least one factor's weight to a non-zero value.</div>
+    </div>
+    <div ng-repeat="factor in PraAdjusterService.factors">
+      <div class="d-flex flex-row">
+        <div class="p-2 flex-grow-1">
+          <span class="glyphicon cursor-pointer"
+            ng-class="datasetlistVisible ? 'icon-chevron-down' : 'icon-chevron-right'"
+            ng-click="datasetlistVisible = !datasetlistVisible"></span>
+          <label class="cursor-pointer" ng-click="datasetlistVisible = !datasetlistVisible">{{factor.name}}</label>
         </div>
-        <input type="range" class="custom-range" ng-model="factor.weight" min="0"
-          max="1.0" step="0.05">
-        <div ng-init="datasetlistVisible = false" ng-show="datasetlistVisible">
-          <div ng-repeat="dataset in factor.datasets">
-            <button type="button" class="btn btn-sm btn-light hs-lm-item-visibility"
-              ng-click="dataset.included = !dataset.included;$event.stopPropagation()"
-              ng-class="dataset.included ? 'hs-checkmark' : 'hs-uncheckmark'"></button>
-            <label>{{dataset.name}}</label>
+        <div class="p-2">{{(factor.weight * 100).toFixed(0)}}&nbsp;%</div>
+      </div>
+      <input type="range" class="custom-range" ng-model="factor.weight" min="0" max="1.0" step="0.05">
+      <div ng-init="datasetlistVisible = false" ng-show="datasetlistVisible">
+        <div ng-repeat="dataset in factor.datasets">
+          <button type="button" class="btn btn-sm btn-light hs-lm-item-visibility"
+            ng-class="dataset.included ? 'hs-checkmark' : 'hs-uncheckmark'"
+            ng-click="dataset.included = !dataset.included;$event.stopPropagation()"></button>
+          <span class="glyphicon cursor-pointer text-secondary"
+            ng-class="descriptionVisible ? 'icon-chevron-down' : 'icon-chevron-right'"
+            ng-click="descriptionVisible = !descriptionVisible"></span>
+          <label class="pl-2 cursor-pointer text-secondary" ng-click="descriptionVisible = !descriptionVisible">{{dataset.name}}</label>
+          <div class="p-2 mb-2 text-justify text-info" ng-show="descriptionVisible">
+            {{dataset.desc}}
           </div>
         </div>
       </div>

+ 3 - 3
src/adjuster/adjuster.module.js

@@ -1,7 +1,7 @@
 import {AdjusterComponent} from './adjuster.component.js';
 import {AdjusterService} from './adjuster.service.js';
 
-angular
+export const AdjusterModule = angular
   .module('pra.adjuster', ['hs.core', 'hs.map'])
   .directive('praAdjusterSidebarBtn', function () {
     return {
@@ -9,9 +9,9 @@ angular
     };
   })
   .service('PraAdjusterService', AdjusterService)
-  .component('pra.adjuster', AdjusterComponent)
+  .component('praAdjuster', AdjusterComponent)
   .directive('praAdjusterLoader', () => {
     return {
       template: require('./adjuster-loader.directive.html'),
     };
-  });
+  }).name;

+ 63 - 46
src/adjuster/adjuster.service.js

@@ -1,20 +1,21 @@
 // import attractivity from '../Attractivity.json';
 import nuts from '../nuts.js';
-import {factors} from './factors.js';
+//import {factors} from './factors.js';
 
 export class AdjusterService {
   constructor(HsCore, HsUtilsService, $rootScope, $http, $location) {
     'ngInject';
-    this.HsCore = HsCore;
+    //this.HsCore = HsCore;
     this.HsUtilsService = HsUtilsService;
     this.$rootScope = $rootScope;
     this.$http = $http;
     this.serviceBaseUrl =
       $location.host() === 'localhost'
-        ? 'https://jmacura.ml/ws/'
+        ? 'https://jmacura.ml/ws/' // 'http://localhost:3000/'
         : 'https://publish.lesprojekt.cz/nodejs/';
-    this.factors = factors;
+    this.factors = [];
     this.clusters = [];
+    this.method = 'haclust';
     this._clusteringInProcess = true;
     this.init();
   }
@@ -27,22 +28,23 @@ export class AdjusterService {
     const f = () => {
       this._clusteringInProcess = true;
       this.$http({
-        method: 'get',
+        method: 'post',
         url: this.serviceBaseUrl + 'clusters',
-        /*data: {
+        data: {
           factors: this.factors.map((f) => {
             return {
-              factor: f.factor,
+              factor: f.name,
               weight: f.weight,
               datasets: f.datasets
                 .filter((ds) => ds.included)
                 .map((ds) => ds.name),
             };
           }),
-        },*/
-      }).then((response) => {
-        const clusterData = response.data.response;
-        /*let max = 0;
+        },
+      })
+        .then((response) => {
+          const clusterData = response.data.response;
+          /*let max = 0;
         this.clusters.forEach((a) => {
           if (a.aggregate > max) {
             max = a.aggregate;
@@ -55,27 +57,33 @@ export class AdjusterService {
         this.attractivity.forEach((a) => {
           this.nutsCodeRecordRelations[a.code] = a;
         });*/
-        nuts.nuts3Source.forEachFeature((feature) => {
-          // Pair each feature with its clustering data
-          const featureData = clusterData.find(
-            (item) => item['nuts_id'] === feature.get('NUTS_ID')
-          );
-          Object.keys(featureData).forEach(function (key, index) {
-            if (key !== 'nuts_id') {
-              feature.set(key, featureData[key]);
-            }
+          nuts.nuts3Source.forEachFeature((feature) => {
+            // Pair each feature with its clustering data
+            const featureData = clusterData.find(
+              (item) => item['nuts_id'] === feature.get('NUTS_ID')
+            );
+            Object.keys(featureData).forEach(function (key, index) {
+              if (key !== 'nuts_id') {
+                feature.set(key, featureData[key]);
+              }
+            });
           });
-        });
-        const clusters = [];
-        for (const region of clusterData) {
-          if (!clusters.includes(region['km25.cluster'])) {
-            clusters.push(region['km25.cluster']);
+          const clusters = [];
+          for (const region of clusterData) {
+            if (!clusters.includes(region[this.method])) {
+              clusters.push(region[this.method]);
+            }
           }
-        }
-        this.clusters = clusters;
-        this._clusteringInProcess = false;
-        this.$rootScope.$broadcast('clusters_loaded');
-      });
+          this.clusters = clusters;
+          this._clusteringInProcess = false;
+          this.$rootScope.$broadcast('clusters_loaded');
+        })
+        .catch((error) => {
+          console.error(
+            `Error obtaining data from ${this.serviceBaseUrl}: ${error}`
+          );
+          this._clusteringInProcess = false;
+        });
     };
     this.HsUtilsService.debounce(f, 300)();
   }
@@ -83,23 +91,32 @@ export class AdjusterService {
   init() {
     this.$http({
       url: this.serviceBaseUrl + 'datasets',
-    }).then((response) => {
-      this.factors = response.data.map((dataset) => {
-        return {name: dataset.Factor, weight: 1, datasets: []};
-      });
-      this.factors = this.HsUtilsService.removeDuplicates(this.factors, 'name');
-      this.factors.forEach((factor) => {
-        factor.datasets = response.data
-          .filter((ds) => ds.Factor === factor.name)
-          .map((ds) => {
-            return {
-              name: ds.Name,
-              included: true,
-            };
-          });
+    })
+      .then((response) => {
+        this.factors = response.data.map((dataset) => {
+          return {name: dataset.Factor, weight: 1, datasets: []};
+        });
+        this.factors = this.HsUtilsService.removeDuplicates(
+          this.factors,
+          'name'
+        );
+        this.factors.forEach((factor) => {
+          factor.datasets = response.data
+            .filter((ds) => ds.Factor === factor.name)
+            .map((ds) => {
+              return {
+                name: ds.Name,
+                desc: ds.Description,
+                included: true,
+              };
+            });
+        });
+        this.apply();
+      })
+      .catch((error) => {
+        console.error(`Web service at ${this.serviceBaseUrl} unavailable!`);
+        this._clusteringInProcess = false;
       });
-      this.apply();
-    });
   }
 
   /**

+ 32 - 20
src/app.component.js

@@ -12,6 +12,7 @@ export const HsComponent = {
   controller: function (
     $scope,
     $compile,
+    $element,
     gettext,
     HsCore,
     HsLayoutService,
@@ -23,23 +24,27 @@ export const HsComponent = {
     vm.exists = HsCore.exists;
     vm.panelVisible = HsLayoutService.panelVisible;
     vm.PraAdjusterService = PraAdjusterService;
+    HsLayoutService.fullScreenMap($element, HsCore);
     HsLayoutService.sidebarRight = false;
     // HsLayoutService.sidebarToggleable = false;
     // HsCore.singleDatasources = true;
-    HsLayoutService.sidebarButtons = true;
-    HsLayoutService.setDefaultPanel('adjuster');
+    //HsLayoutService.sidebarButtons = true;
+    HsSidebarService.buttons.push({
+      panel: 'adjuster',
+      module: 'pra.adjuster',
+      order: '6',
+      title: 'Adjust factors',
+      description: '',
+      icon: 'icon-analytics-piechart',
+    });
     $scope.$on('scope_loaded', function (event, args) {
       if (args === 'Sidebar') {
         const el = angular.element(
-          '<pra.adjuster hs.draggable ng-if="vm.exists(\'pra.adjuster\')" ng-show="vm.panelVisible(\'adjuster\', this)"></pra.adjuster>'
+          '<pra-adjuster hs.draggable ng-if="vm.exists(\'pra.adjuster\')" ng-show="vm.panelVisible(\'adjuster\', this)"></pra-adjuster>'
         )[0];
         HsLayoutService.panelListElement.appendChild(el);
         $compile(el)($scope);
-        const toolbarButton = angular.element(
-          '<div pra-adjuster-sidebar-btn></div>'
-        )[0];
-        HsLayoutService.sidebarListElement.appendChild(toolbarButton);
-        $compile(toolbarButton)(event.targetScope);
+        HsLayoutService.setDefaultPanel('adjuster');
         const loader = angular.element(
           '<pra-adjuster-loader class="loader-splash" ng-if="vm.PraAdjusterService.isClusteringInProcess()"></pra-adjuster-loader>'
         )[0];
@@ -60,28 +65,34 @@ export const HsComponent = {
 
 let randomColorPalette = [];
 
-const stroke = new Stroke({
-  color: '#3399CC',
-  width: 0.25,
+const nuts2style = new Style({
+  stroke: new Stroke({
+    color: '#FFFFFF',
+    width: 0.5,
+  }),
 });
 
-const styles = function (feature) {
-  if (isNaN(feature.get('km25.cluster'))) {
+// TODO: 'method' must be obtained from AdjusterService
+const method = 'haclust';
+const nuts3style = function (feature) {
+  if (isNaN(feature.get(method))) {
     return [
       new Style({
         fill: new Fill({
           color: '#FFF',
         }),
-        stroke: stroke,
+        stroke: new Stroke({
+          color: '#3399CC',
+          width: 0.25,
+        }),
       }),
     ];
   } else {
     return [
       new Style({
         fill: new Fill({
-          color: randomColorPalette[feature.get('km25.cluster') - 1],
+          color: randomColorPalette[feature.get(method) - 1],
         }),
-        //stroke: stroke,
       }),
     ];
   }
@@ -89,8 +100,9 @@ const styles = function (feature) {
 
 export const nuts2Layer = new VectorLayer({
   source: nuts.nuts2Source,
-  visible: false,
-  style: styles,
+  editor: {editable: false},
+  visible: true,
+  style: nuts2style,
   title: 'NUTS2 regions',
 });
 
@@ -98,14 +110,14 @@ export const nuts3Layer = new VectorLayer({
   source: nuts.nuts3Source,
   editor: {editable: false},
   visible: true,
-  style: styles,
+  style: nuts3style,
   title: 'NUTS3 regions',
 });
 nuts3Layer.set('popUp', {
   attributes: [
     {attribute: 'CNTR_CODE', label: 'Country'},
     {attribute: 'NUTS_NAME', label: 'Name'},
-    {attribute: 'km25.cluster', label: 'Cluster ID'},
+    {attribute: method, label: 'Cluster ID'},
   ],
 });
 

+ 7 - 2
src/app.css

@@ -1,7 +1,12 @@
+html, body {
+  margin: 0;
+  padding: 0;
+}
+
 #hs-app {
-  display: block;
-  height: 100vh;
   position: relative;
+  margin: 0;
+  padding: 0;
 }
 
 .cursor-pointer {

+ 13 - 5
src/app.module.js

@@ -14,10 +14,10 @@ import 'hslayers-ng/components/sidebar/sidebar.module';
 import 'hslayers-ng/components/toolbar/toolbar.module';
 // hslayers-ng components must be loaded first, otherwise angular will be undefined
 // eslint-disable-next-line sort-imports-es6-autofix/sort-imports-es6
-import './adjuster/adjuster.module.js';
 import './app.css';
 import View from 'ol/View';
 import angular from 'angular';
+import {AdjusterModule} from './adjuster/adjuster.module.js';
 import {HsComponent, nuts2Layer, nuts3Layer} from './app.component.js';
 import {OSM} from 'ol/source';
 import {Tile} from 'ol/layer';
@@ -47,19 +47,26 @@ angular
     'hs.sidebar',
     'hs.toolbar',
     'gettext',
-    'pra.adjuster',
+    AdjusterModule,
   ])
   .value('HsConfig', {
     //proxyPrefix: '../8085/',
     default_layers: [
       new Tile({
-        source: new OSM(),
+        source: new OSM({
+          url:
+            'https://cartodb-basemaps-{a-d}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png',
+          attributions: [
+            '© <a href="https://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a> contributors,',
+            '<a href="https://carto.com/attributions">CARTO</a>',
+          ],
+        }),
         title: 'OpenStreetMap',
         base: true,
         removable: false,
       }),
-      nuts2Layer,
       nuts3Layer,
+      nuts2Layer,
     ],
     popUpDisplay: 'hover',
     project_name: 'erra/map',
@@ -83,8 +90,9 @@ angular
     panelsEnabled: {
       composition_browser: false,
       saveMap: false,
+      permalink: false,
       language: false,
     },
     sizeMode: 'fullscreen',
   })
-  .component('hs', HsComponent).name;
+  .component('hs', HsComponent);

Неке датотеке нису приказане због велике количине промена