Select Git revision
results-map.component.ts
-
zdenko.vuk@ijs.si authored
css update wip update update removed temporary content made some changes on setlegend. Page currently loads cpu at 100% for a while, dunno why also removed two logs
zdenko.vuk@ijs.si authoredcss update wip update update removed temporary content made some changes on setlegend. Page currently loads cpu at 100% for a while, dunno why also removed two logs
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) +
"'>■</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) +
"'>■</span> " +
grades[i] +
(grades[i + 1] ? "–" + 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) +
"'>■</span> " +
grades[i] +
(grades[i + 1] ? "–" + grades[i + 1] + "</p>" : "+</p>");
}
div.innerHTML +=
"<p style='background:white; display: inline; font-size:17px'> <span style='color:" +
MapColors.getColorForLegend(5, grades) +
"'>■</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 = "";
}
}