Skip to content
Snippets Groups Projects
Select Git revision
  • d717c072f015ebe1435650c67687c5da9eccd2f7
  • master default
2 results

results-map.component.ts

Blame
  • results-map.component.ts 21.53 KiB
    import { Component, Input, TemplateRef, OnChanges } from "@angular/core";
    import * as L from "leaflet";
    import { SimulationService } from "../utils/services/simulation.service";
    import * as MapColors from "./map-colors-local";
    import { legendInverted, ratiosBetweenGrades } from "./results-map-settings";
    import { NbToastrService, NbDialogService } from "@nebular/theme";
    import { Simulation } from "../utils/data/simulation";
    
    @Component({
      selector: "ngx-results-map",
      templateUrl: "./results-map.component.html",
      styleUrls: ["./results-map.component.scss"],
    })
    export class ResultsMapComponent implements OnChanges {
      public map: L.Map;
      options = {
        layers: [
          L.tileLayer("http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
            maxZoom: 18,
            attribution: "...",
          }),
        ],
        zoom: 5,
        center: L.latLng({ lat: 50, lng: 14 }),
      };
      mapLegend = new L.Control({ position: "bottomright" });
      radioButtonLayersControl: any;
      loadGeojsonButtons: string[] = [
        "bikeability",
        "bikeIntensity",
        "bikeSafety",
        "pollution",
      ];
      checkedRadioButton: string = "";
      geoJsonStyleWeight: number = 5;
      geoJsonStyleOpacity: number = 0.8;
      maxKeyValues: Object = {};
      grayscale: any = 50;
      geojsonLoading: boolean = false;
      @Input() selectedSimulation: Simulation;
      selectedGeojson: string = "";
    
      constructor(
        private simulationService: SimulationService,
        private toastrService: NbToastrService,
        private dialogService: NbDialogService
      ) {}
      onMapReady(map: L.Map) {
        this.map = map;
        this.grayscaleChange();
        setTimeout(() => {
          this.map.invalidateSize();
        }, 50);
        // setTimeout(() => {
        //   console.log(
        //     "%cTemporary",
        //     "background: crimson; font-weight: bold; color: black; font-family: serif; padding: 0 10px;"
        //   );
        //   const celkomplet: HTMLElement = document.querySelectorAll(
        //     ".ng-tns-c441-0.appearance-filled.size-medium.shape-rectangle.status-primary.ng-star-inserted.nb-transition"
        //   )[1] as HTMLElement;
        //   celkomplet.click();
        // }, 995);
        // setTimeout(() => {
        //   console.log(
        //     "%cTemporary",
        //     "background: peru; font-weight: bold; color: black; font-family: serif; padding: 0 10px;"
        //   );
        //   const celkomplet: HTMLElement = document.querySelectorAll(
        //     ".buttons.ng-tns-c441-0"
        //   )[0] as HTMLElement;
        //   celkomplet.style.display = "none";
        // }, 1000);
      }
    
      onLoad(selectedKpi: string) {
        this.selectedGeojson = selectedKpi;
        this.resetLayersAndButton();
        this.setMapZoom();
    
        if (this.radioButtonLayersControl)
          this.radioButtonLayersControl.remove(this.map);
    
        this.toastrService.show("This might take a while", "Info", {
          status: "info",
        });
        this.geojsonLoading = true;
        this.simulationService
          .getGeojson(this.selectedSimulation["id"].toString(), selectedKpi)
          .subscribe(
            (res) => {
              if (res["message"] == "Calculating...") {
                this.toastrService.show(
                  "Server says: Calculating the KPI visualization. GEOJSON loading will not be completed.",
                  "Geojson not ready",
                  {
                    status: "danger",
                    duration: 10000,
                  }
                );
                this.geojsonLoading = false;
                return;
              } else if (res["message"] == "Calculating the KPI visualization.") {
                this.toastrService.show(
                  "Server says: Calculating the KPI visualization. GEOJSON loading will not be completed.",
                  "Geojson not ready",
                  {
                    status: "danger",
                    duration: 10000,
                  }
                );
                this.geojsonLoading = false;
                return;
              } else if (res["message"] == "Please prepare visualizations first.") {
                this.toastrService.show(
                  "The visualization is not prepared yet, try again in a few minutes.",
                  "Geojson not ready",
                  {
                    status: "danger",
                    duration: 10000,
                  }
                );
                this.geojsonLoading = false;
                return;
              } else if (res["message"] && res["message"].length > 0) {
                this.toastrService.show(
                  "Geojson might not be ready",
                  "API response contains a message",
                  {
                    status: "warning",
                  }
                );
              }
    
              this.toastrService.show("", "Geojson Loaded", {
                status: "success",
              });
    
              const keys = this.extractKeys(res);
              const propertyGeojsons = this.populatePropertyGeojsons(res, keys);
              let obj = {};
    
              for (let i = 0; i < propertyGeojsons.length; i++) {
                if (
                  propertyGeojsons[i].myKey == "link_id" ||
                  propertyGeojsons[i].myKey == "modes" ||
                  propertyGeojsons[i].myKey == "internal_travel_by_mode"
                )
                  continue;
                obj[`${keys[i]}`] = propertyGeojsons[i];
              }
              this.radioButtonLayersControl = new L.Control.Layers(obj, null, {
                collapsed: false,
              });
              this.map.addControl(this.radioButtonLayersControl);
              this.addListenerToRadioButtonLayersControl();
              this.geojsonLoading = false;
            },
            (error) => {
              this.toastrService.show(error["message"], "Error Getting Geojson", {
                status: "danger",
              });
              this.geojsonLoading = false;
            }
          );
      }
    
      setLegend(legendType) {
        if (this.map != undefined && legendType == undefined) {
          this.map.removeControl(this.mapLegend);
          return;
        }
        if (this.map == undefined) {
          return;
        }
    
        this.mapLegend.onAdd = (map) => {
          let direction: string;
          if (legendInverted.indexOf(legendType) == -1) {
            direction = "lowToHigh";
          } else {
            direction = "highToLow";
          }
    
          if (
            this.maxKeyValues["maxValue_" + legendType] == undefined ||
            this.maxKeyValues["maxValue_" + legendType] == 0
          ) {
            this.toastrService.info(
              "Geojson not displayed because all values are undefined or zero!",
              "Geojson not displayed"
            );
            let div = L.DomUtil.create("div");
            div.innerHTML += `<p style='background:white; display: inline; font-size:17px; padding: 0 7px; background: #ff3d71'>Values for ${legendType} kpi are undefined or zero!</p>`;
            return div;
          }
    
          var div = L.DomUtil.create("div");
          const grades = [
            0,
            this.limitMyDigits(
              this.maxKeyValues["maxValue_" + legendType] / ratiosBetweenGrades[0]
            ),
            this.limitMyDigits(
              this.maxKeyValues["maxValue_" + legendType] / ratiosBetweenGrades[1]
            ),
            this.limitMyDigits(
              this.maxKeyValues["maxValue_" + legendType] / ratiosBetweenGrades[2]
            ),
            this.limitMyDigits(
              this.maxKeyValues["maxValue_" + legendType] / ratiosBetweenGrades[3]
            ),
            this.limitMyDigits(
              this.maxKeyValues["maxValue_" + legendType] / ratiosBetweenGrades[4]
            ),
          ];
    
          if (direction == "lowToHigh") {
            div.innerHTML +=
              "<p style='background:white; display: inline; font-size:17px'> <span style='color:" +
              MapColors.getColorForLegend(0, grades) +
              "'>&#9632</span> <" +
              grades[1] +
              "</p>";
            for (let i = 1; i < 6; i++) {
              div.innerHTML +=
                "<p style='background:white; display: inline; font-size:17px'> <span style='color:" +
                MapColors.getColorForLegend(i, grades) +
                "'>&#9632</span> " +
                grades[i] +
                (grades[i + 1] ? "&ndash;" + grades[i + 1] + "</p>" : "+</p>");
            }
          } else {
            for (let i = 5; i > 0; i--) {
              div.innerHTML +=
                "<p style='background:white; display: inline; font-size:17px'> <span style='color:" +
                MapColors.getColorForLegend(5 - i, grades) +
                "'>&#9632</span> " +
                grades[i] +
                (grades[i + 1] ? "&ndash;" + grades[i + 1] + "</p>" : "+</p>");
            }
            div.innerHTML +=
              "<p style='background:white; display: inline; font-size:17px'> <span style='color:" +
              MapColors.getColorForLegend(5, grades) +
              "'>&#9632</span> <" +
              grades[1] +
              "</p>";
          }
    
          return div;
        };
        this.mapLegend.addTo(this.map);
      }
    
      private highlightFeature(e, feature) {
        const layer = e.target;
        let text = "";
        if (feature["capacity"]) {
          if (feature["capacity"] == "9999.0" || feature["capacity"] == "9999") {
            text += `<span style='display: inline-block; margin-bottom: 7px; font-weight: bold;'>Oops! Looks like you clicked on an transparent bus line with capacity of 9999!</span><br>`;
          }
          const tmp = this.limitMyDigits(feature["capacity"]);
          if ("capacity" == this.checkedRadioButton) {
            text += `<u><strong>capacity:</strong> ${tmp}</u><br>`;
          } else {
            text += `<strong>capacity:</strong> ${tmp}<br>`;
          }
          text += `<span style='display: inline-block; margin-bottom: 7px; color: #666'>(unrounded) ${feature["capacity"]}</span><br>`;
        }
    
        if (feature["cityWide"]) {
          for (let p in feature["cityWide"]["pollution"]) {
            const tmp = this.limitMyDigits(feature["cityWide"]["pollution"][p]);
            if (p == this.checkedRadioButton) {
              text += `<u><strong>${p}:</strong> ${tmp}</u><br>`;
            } else {
              text += `<strong>${p}:</strong> ${tmp}<br>`;
            }
            text += `<span style='display: inline-block; margin-bottom: 7px; color: #666'>(unrounded) ${feature["cityWide"]["pollution"][p]}</span><br>`;
          }
          for (let p in feature["cityWide"]["traffic"]) {
            const tmp = this.limitMyDigits(feature["cityWide"]["traffic"][p]);
            if (p == this.checkedRadioButton) {
              text += `<u><strong>${p}:</strong> ${tmp}</u><br>`;
            } else {
              text += `<strong>${p}:</strong> ${tmp}<br>`;
            }
            text += `<span style='display: inline-block; margin-bottom: 7px; color: #666'>(unrounded) ${feature["cityWide"]["traffic"][p]}</span><br>`;
          }
        }
    
        Object.entries(feature).forEach((element) => {
          if (element[0] == this.checkedRadioButton) {
            text += `<u><strong>${element[0]}:</strong> ${element[1]}</u><br>`;
          } else {
            text += `<strong>${element[0]}:</strong> ${element[1]}<br>`;
          }
          text += `<span style='display: inline-block; margin-bottom: 7px; color: #666'>(unrounded) ${element[1]}</span><br>`;
        });
    
        if (!text.length)
          text = "<span style='color: lightslategray';>No data available.</span>";
        layer.bindPopup(text);
        layer.setStyle({
          weight: 10,
          opacity: 1.0,
        });
      }
    
      private resetFeature(e, feature) {
        const layer = e.target;
    
        layer.setStyle({
          weight: this.geoJsonStyleWeight,
          opacity: this.geoJsonStyleOpacity,
        });
      }
    
      public extractKeys(jsonFile) {
        let allKeys = [];
        for (let f = 0; f < jsonFile["features"].length && f < 3498; f++) {
          let iterator;
          Object.keys(jsonFile["features"][f]["properties"]).forEach((element) => {
            if (!allKeys.includes(element)) {
              allKeys.push(element);
            }
          });
          if (jsonFile["features"][f]["properties"]["capacity"]) {
            if (!allKeys.includes("capacity")) {
              allKeys.push("capacity");
            }
          }
          if (
            jsonFile["features"][f]["properties"]["cityWide"] &&
            jsonFile["features"][f]["properties"]["cityWide"]["pollution"]
          ) {
            iterator = Object.keys(
              jsonFile["features"][f]["properties"]["cityWide"]["pollution"]
            );
            for (let g = 0; g < iterator.length; g++) {
              if (!allKeys.includes(iterator[g])) {
                allKeys.push(iterator[g]);
              }
            }
          }
          if (
            jsonFile["features"][f]["properties"]["cityWide"] &&
            jsonFile["features"][f]["properties"]["cityWide"]["traffic"]
          ) {
            iterator = Object.keys(
              jsonFile["features"][f]["properties"]["cityWide"]["traffic"]
            );
            for (let g = 0; g < iterator.length; g++) {
              if (!allKeys.includes(iterator[g])) {
                allKeys.push(iterator[g]);
              }
            }
          }
        }
        return allKeys;
      }
    
      public populatePropertyGeojsons(jsonFile, keys) {
        let res = [];
        this.populateMaxKeyValues(jsonFile, keys);
    
        for (let f = 0; f < keys.length; f++) {
          let tmp: Object = {};
    
          if (
            this.maxKeyValues["maxValue_" + keys[f]] == undefined ||
            this.maxKeyValues["maxValue_" + keys[f]] == 0
          ) {
            tmp = L.geoJSON(<any>{
              type: "FeatureCollection",
              features: [],
            });
          } else {
            if (keys[f] == "capacity") {
              tmp = L.geoJSON(<any>jsonFile, {
                style: (feature) => ({
                  weight: this.geoJsonStyleWeight,
                  color: MapColors.getColorHighToLow(
                    feature["properties"]["capacity"],
                    keys[f],
                    this.maxKeyValues[`maxValue_${keys[f]}`],
                    ratiosBetweenGrades,
                    parseInt(feature["properties"]["capacity"]) == 9999
                      ? true
                      : false
                  ),
                  opacity: this.geoJsonStyleOpacity,
                }),
                onEachFeature: (feature, layer) =>
                  layer.on({
                    mouseover: (e) => this.highlightFeature(e, feature.properties),
                    mouseout: (e) => this.resetFeature(e, feature.properties),
                  }),
              });
            } else if (keys[f] == "dailyInternalBikeTravels") {
              tmp = L.geoJSON(<any>jsonFile, {
                style: (feature) => ({
                  weight: this.geoJsonStyleWeight,
                  color: MapColors.getColorHighToLow(
                    feature["properties"]["cityWide"]["traffic"]
                      .dailyInternalBikeTravels ||
                      feature["properties"].dailyInternalBikeTravels,
                    keys[f],
                    this.maxKeyValues[`maxValue_${keys[f]}`],
                    ratiosBetweenGrades,
                    parseInt(feature["properties"]["capacity"]) == 9999
                      ? true
                      : false
                  ),
                  opacity: this.geoJsonStyleOpacity,
                }),
                onEachFeature: (feature, layer) =>
                  layer.on({
                    mouseover: (e) => this.highlightFeature(e, feature.properties),
                    mouseout: (e) => this.resetFeature(e, feature.properties),
                  }),
              });
            } else if (keys[f] == "pedestrianTravelTime") {
              tmp = L.geoJSON(<any>jsonFile, {
                style: (feature) => ({
                  weight: this.geoJsonStyleWeight,
                  color: MapColors.getColor(
                    feature["properties"]["cityWide"]["traffic"]
                      .pedestrianTravelTime ||
                      feature["properties"].pedestrianTravelTime,
                    keys[f],
                    this.maxKeyValues[`maxValue_${keys[f]}`],
                    ratiosBetweenGrades,
                    parseInt(feature["properties"]["capacity"]) == 9999
                      ? true
                      : false
                  ),
                  opacity: this.geoJsonStyleOpacity,
                }),
                onEachFeature: (feature, layer) =>
                  layer.on({
                    mouseover: (e) => this.highlightFeature(e, feature.properties),
                    mouseout: (e) => this.resetFeature(e, feature.properties),
                  }),
              });
            } else {
              tmp = L.geoJSON(<any>jsonFile, {
                style: (feature) => ({
                  weight: this.geoJsonStyleWeight,
                  color: MapColors.getColor(
                    feature["properties"],
                    keys[f],
                    this.maxKeyValues[`maxValue_${keys[f]}`],
                    ratiosBetweenGrades,
                    parseInt(feature["properties"]["capacity"]) == 9999
                      ? true
                      : false
                  ),
                  opacity: this.geoJsonStyleOpacity,
                }),
                onEachFeature: (feature, layer) =>
                  layer.on({
                    mouseover: (e) => this.highlightFeature(e, feature.properties),
                    mouseout: (e) => this.resetFeature(e, feature.properties),
                  }),
              });
            }
          }
          tmp["myKey"] = keys[f];
          res.push(tmp);
        }
        return res;
      }
    
      public addListenerToRadioButtonLayersControl() {
        this.map.on("baselayerchange", (e) => {
          this.setLegend(e["name"]);
          this.checkedRadioButton = e["name"];
        });
      }
    
      public populateMaxKeyValues(jsonFile: Object, keys: Object[]) {
        for (let g = 0; g < jsonFile["features"].length; g++) {
          for (let f = 0; f < keys.length; f++) {
            if (
              jsonFile["features"][g]["properties"]["cityWide"] &&
              jsonFile["features"][g]["properties"]["cityWide"]["pollution"]
            ) {
              if (this.maxKeyValues[`maxValue_${keys[f]}`] == undefined) {
                this.maxKeyValues[`maxValue_${keys[f]}`] =
                  jsonFile["features"][g]["properties"]["cityWide"]["pollution"][
                    keys[f]
                  ];
              }
              if (
                jsonFile["features"][g]["properties"]["cityWide"]["pollution"][
                  keys[f]
                ] > this.maxKeyValues[`maxValue_${keys[f]}`]
              ) {
                this.maxKeyValues[`maxValue_${keys[f]}`] =
                  jsonFile["features"][g]["properties"]["cityWide"]["pollution"][
                    keys[f]
                  ];
              }
            }
            if (
              jsonFile["features"][g]["properties"]["cityWide"] &&
              jsonFile["features"][g]["properties"]["cityWide"]["traffic"]
            ) {
              if (this.maxKeyValues[`maxValue_${keys[f]}`] == undefined) {
                this.maxKeyValues[`maxValue_${keys[f]}`] =
                  jsonFile["features"][g]["properties"]["cityWide"]["traffic"][
                    keys[f]
                  ];
              }
              if (
                jsonFile["features"][g]["properties"]["cityWide"]["traffic"][
                  keys[f]
                ] > this.maxKeyValues[`maxValue_${keys[f]}`]
              ) {
                this.maxKeyValues[`maxValue_${keys[f]}`] =
                  jsonFile["features"][g]["properties"]["cityWide"]["traffic"][
                    keys[f]
                  ];
              }
            }
            if (jsonFile["features"][g]["properties"]["capacity"]) {
              const parsed = parseInt(
                jsonFile["features"][g]["properties"]["capacity"]
              );
              if (parsed != 9999) {
                if (this.maxKeyValues[`maxValue_capacity`] == undefined) {
                  this.maxKeyValues[`maxValue_capacity`] = parsed;
                }
                if (parsed > this.maxKeyValues[`maxValue_capacity`]) {
                  this.maxKeyValues[`maxValue_capacity`] = parsed;
                }
              }
            }
            if (jsonFile["features"][g]["properties"][keys[f]]) {
              if (this.maxKeyValues[`maxValue_${keys[f]}`] == undefined) {
                this.maxKeyValues[`maxValue_${keys[f]}`] =
                  jsonFile["features"][g]["properties"][keys[f]];
              }
              if (
                jsonFile["features"][g]["properties"][keys[f]] >
                this.maxKeyValues[`maxValue_${keys[f]}`]
              ) {
                this.maxKeyValues[`maxValue_${keys[f]}`] =
                  jsonFile["features"][g]["properties"][keys[f]];
              }
            }
          }
        }
      }
    
      public limitMyDigits(n: number): number {
        if (n > 1000) {
          return Math.round(n);
        } else if (n > 100) {
          return Math.round(n);
        } else if (n > 10) {
          return Math.floor(Math.round(n * 10)) / 10;
        } else if (n > 1) {
          return Math.floor(Math.round(n * 100)) / 100;
        } else if (n > 0.1) {
          return Math.floor(Math.round(n * 1000)) / 1000;
        } else if (n > 0.01) {
          return Math.floor(Math.round(n * 10000)) / 10000;
        } else if (n > 0.001) {
          return Math.floor(Math.round(n * 100000)) / 100000;
        } else if (n > 0.0001) {
          return Math.floor(Math.round(n * 1000000)) / 1000000;
        } else if (n > 0.00001) {
          return Math.floor(Math.round(n * 10000000)) / 10000000;
        } else {
          return n;
        }
      }
    
      grayscaleChange() {
        const el = <HTMLElement>(
          document.querySelectorAll(".leaflet-pane.leaflet-tile-pane")[0]
        );
        el.style.filter = "grayscale(" + (100 - this.grayscale) + "%)";
      }
    
      setMapZoom() {
        if (this.selectedSimulation["city"]["cityId"] == "amsterdam") {
          this.map.setView(new L.LatLng(52.4, 4.9), 9);
        } else if (this.selectedSimulation["city"]["cityId"] == "bilbao") {
          this.map.setView(new L.LatLng(43.3, -2.9), 9);
        } else if (this.selectedSimulation["city"]["cityId"] == "helsinki") {
          this.map.setView(new L.LatLng(60.2, 24.9), 9);
        } else if (this.selectedSimulation["city"]["cityId"] == "messina") {
          this.map.setView(new L.LatLng(38.2, 15.6), 9);
        } else {
          this.toastrService.show("Unexpected value for city name.", "Error", {
            status: "danger",
          });
        }
      }
    
      openOverviewOfIndicators(dialog: TemplateRef<any>) {
        this.dialogService.open(dialog, {
          context: this.selectedSimulation["city"]["cityId"],
        });
      }
    
      resetLayersAndButton() {
        let timeout = 100;
        if (this.map != undefined) {
          this.map.eachLayer((layer) => {
            if (layer["feature"] != undefined) {
              setTimeout(() => {
                this.map.removeLayer(layer);
                timeout += 50;
              }, timeout);
            }
          });
    
          if (this.radioButtonLayersControl)
            this.radioButtonLayersControl.remove(this.map);
        }
        this.setLegend(undefined);
      }
    
      ngOnChanges() {
        this.resetLayersAndButton();
        this.selectedGeojson = "";
      }
    }