diff --git a/backend/prepare.py b/backend/prepare.py new file mode 100644 index 0000000..97dddd3 --- /dev/null +++ b/backend/prepare.py @@ -0,0 +1,56 @@ +import geopandas as gpd +import shapely.geometry as geo +import numpy as np +import pandas as pd +from tqdm import tqdm +import pickle + +center = geo.Point(651791.0, 6862293.0) +radius = 10000 + +df_iris = gpd.read_file("../data/CONTOURS-IRIS.shp") +df_iris = df_iris[df_iris.geometry.centroid.distance(center) < radius] + +df_iris = df_iris[["CODE_IRIS", "geometry"]].rename( + columns = { "CODE_IRIS": "iris_id" } +) + +df_municipality = df_iris.copy() +df_municipality["municipality_id"] = df_municipality["iris_id"].str[:-4] +del df_municipality["iris_id"] +df_municipality = df_municipality.dissolve("municipality_id").reset_index() + +df_trips = pd.read_csv("../data/trips_alpha1.0.csv", sep = ";") + +df_trips["origin_geometry"] = [ + geo.Point(*p) for p in tqdm(zip(df_trips["origin_x"], df_trips["origin_y"]), total = len(df_trips)) +] + +df_trips["destination_geometry"] = [ + geo.Point(*p) for p in tqdm(zip(df_trips["destination_x"], df_trips["destination_y"]), total = len(df_trips)) +] + +df_trips = gpd.GeoDataFrame(df_trips, geometry = "origin_geometry") + +print("Filtering origin geometry") +df_trips = df_trips.set_geometry("origin_geometry") +df_trips = df_trips[df_trips.centroid.distance(center) < radius] + +print("Filtering destination geometry") +df_trips = df_trips.set_geometry("destination_geometry") +df_trips = df_trips[df_trips.centroid.distance(center) < radius] + +print("Joining origin municipality") +df_trips = df_trips.set_geometry("origin_geometry") +df_trips = gpd.sjoin(df_trips, df_municipality, op = "within").drop(["index_right"], axis = 1) +df_trips = df_trips.rename(columns = { "municipality_id": "origin_municipality_id" }) + +print("Joining destination municipality") +df_trips = df_trips.set_geometry("destination_geometry") +df_trips = gpd.sjoin(df_trips, df_municipality, op = "within").drop(["index_right"], axis = 1) +df_trips = df_trips.rename(columns = { "municipality_id": "destination_municipality_id" }) + +with open("../data/trips.p", "wb+") as f: + pickle.dump(df_trips, f) + +print(df_trips) diff --git a/backend/run.py b/backend/run.py index ab45829..ac9a06c 100644 --- a/backend/run.py +++ b/backend/run.py @@ -3,6 +3,7 @@ from flask_cors import CORS from flask import request import matsim +import threading app = Flask(__name__) CORS(app) @@ -12,7 +13,8 @@ import shapely.geometry as geo import numpy as np import pandas as pd -import json +import json, pickle +from tqdm import tqdm center = geo.Point(651791.0, 6862293.0) radius = 10000 @@ -24,6 +26,11 @@ columns = { "CODE_IRIS": "iris_id" } ) +df_municipality = df_iris.copy() +df_municipality["municipality_id"] = df_municipality["iris_id"].str[:-4] +del df_municipality["iris_id"] +df_municipality = df_municipality.dissolve("municipality_id") + df_households = pd.read_csv("../data/output/households.csv", sep = ";") df_persons = pd.read_csv("../data/output/persons.csv", sep = ";") df_activities = gpd.read_file("../data/output/activities.gpkg") @@ -41,6 +48,89 @@ population = matsim.read_population("../data/output_plans.xml.gz") +with open("../data/trips.p", "rb") as f: + df_trips = pickle.load(f) + +class SkimMatrixManager: + def __init__(self, df): + self.df = df + self.matrices = {} + + def calculate(self, identifier, settings): + self.matrices[identifier] = dict( + status = { + "state": "processing", "progress": 0.0 + } + ) + + attribute = settings["attribute"] + metric = settings["metric"] + + df_output = [] + + unique_origins = self.df["origin_municipality_id"].unique() + unique_destinations = self.df["destination_municipality_id"].unique() + + total_count = len(unique_origins) * len(unique_destinations) + current_count = 0 + + destination_filters = {} + + for origin in unique_origins: + f_origin = self.df["origin_municipality_id"] == origin + + for destination in unique_destinations: + if not destination in destination_filters: + destination_filters[destination] = self.df["destination_municipality_id"] == destination + + f_destination = destination_filters[destination] + + df_relation = self.df[f_origin & f_destination] + + df_output.append(dict( + origin_municipality_id = origin, destination_municipality_id = destination, + value = df_relation[attribute].aggregate(metric) + )) + + current_count += 1 + self.matrices[identifier]["status"]["progress"] = current_count / total_count + + print(current_count / total_count) + + df_output = pd.DataFrame.from_records(df_output) + + self.matrices[identifier]["data"] = df_output + self.matrices[identifier]["status"] = dict(state = "done") + + def get_status(self, identifier): + return self.matrices[identifier]["status"] + + def get_data(self, identifier): + return self.matrices[identifier]["data"] + +skim_matrix_manager = SkimMatrixManager(df_trips) +#skim_matrix_manager.calculate("abc", { "attribute": "travel_time", "metric": "mean" }) + +class SkimMatrixLayerStatus(Resource): + def get(self, identifier): + return skim_matrix_manager.get_status(identifier) + +class SkimMatrixLayerCalculate(Resource): + def get(self, identifier, attribute, metric): + threading.Thread(target = lambda: skim_matrix_manager.calculate(identifier, dict( + attribute = attribute, + metric = metric + ))).start() + +class SkimMatrixLayerData(Resource): + def get(self, identifier): + df_data = skim_matrix_manager.get_data(identifier) + return json.loads(df_data.to_json(orient = "records")) + +class SkimMatrixLayerShape(Resource): + def get(self, identifier): + return json.loads(df_municipality.reset_index().to_json()) + def gini(x): """Compute Gini coefficient of array of values""" diffsum = 0 @@ -123,6 +213,10 @@ def transform(iris_id, iris_name, population, shape): api.add_resource(PersonsLayer, '/persons') api.add_resource(ActivitiesLayer, '/activities/') api.add_resource(PopulationLayer, '/population//') +api.add_resource(SkimMatrixLayerStatus, "/skim/status/") +api.add_resource(SkimMatrixLayerCalculate, "/skim/calculate///") +api.add_resource(SkimMatrixLayerData, "/skim/data/") +api.add_resource(SkimMatrixLayerShape, "/skim/shape/") if __name__ == '__main__': app.run(debug = True, host = '0.0.0.0') diff --git a/src/App.vue b/src/App.vue index a444180..eda3b0d 100644 --- a/src/App.vue +++ b/src/App.vue @@ -8,15 +8,17 @@ - + - + + - + - + + @@ -35,13 +37,17 @@ import RequestsLayer from "./components/RequestsLayer.vue" import ActivitiesLayerPanel from "./components/ActivitiesLayerPanel.vue" import ActivitiesLayer from "./components/ActivitiesLayer.vue" +import SkimMatrixLayerPanel from "./components/SkimMatrixLayerPanel.vue" +import SkimMatrixLayer from "./components/SkimMatrixLayer.vue" + import * as axios from "axios"; import * as _ from "lodash"; export default { name: 'App', components: { - ZoneLayerPanel, ZoneLayer, RequestsLayerPanel, RequestsLayer, ActivitiesLayerPanel, ActivitiesLayer + ZoneLayerPanel, ZoneLayer, RequestsLayerPanel, RequestsLayer, ActivitiesLayerPanel, ActivitiesLayer, + SkimMatrixLayerPanel, SkimMatrixLayer }, data() { var layerState = Vue.observable({ @@ -67,8 +73,21 @@ export default { scale: 0.028, offset: [0, 0] }) + var skimLayerState = Vue.observable({ + loading: false, + attribute: undefined, + metric: undefined, + requestedAttribute: "travel_time", + requestedMetric: "mean", + minimumValue: 0, + maximumValue: 3600, + scale: 0.028, offset: [0, 0], + features: [], data: {} + }) + return { layerState: layerState, requestsLayerState: requestsLayerState, activitiesLayerState: activitiesLayerState, + skimLayerState: skimLayerState, scale: 0.028, offset: [0, 0], mouseDownLocation: undefined }; diff --git a/src/components/SkimMatrixLayer.vue b/src/components/SkimMatrixLayer.vue new file mode 100644 index 0000000..e6786a9 --- /dev/null +++ b/src/components/SkimMatrixLayer.vue @@ -0,0 +1,132 @@ + + + + + diff --git a/src/components/SkimMatrixLayerPanel.vue b/src/components/SkimMatrixLayerPanel.vue new file mode 100644 index 0000000..ef2147a --- /dev/null +++ b/src/components/SkimMatrixLayerPanel.vue @@ -0,0 +1,105 @@ + + +