Skip to content

Commit

Permalink
Add skim matrix
Browse files Browse the repository at this point in the history
  • Loading branch information
sebhoerl committed Jun 15, 2020
1 parent f33dd7e commit f76cf92
Show file tree
Hide file tree
Showing 5 changed files with 412 additions and 6 deletions.
56 changes: 56 additions & 0 deletions backend/prepare.py
Original file line number Diff line number Diff line change
@@ -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)
96 changes: 95 additions & 1 deletion backend/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from flask_cors import CORS
from flask import request
import matsim
import threading

app = Flask(__name__)
CORS(app)
Expand All @@ -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
Expand All @@ -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")
Expand All @@ -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
Expand Down Expand Up @@ -123,6 +213,10 @@ def transform(iris_id, iris_name, population, shape):
api.add_resource(PersonsLayer, '/persons')
api.add_resource(ActivitiesLayer, '/activities/<string:person_id>')
api.add_resource(PopulationLayer, '/population/<string:attribute>/<string:metric>')
api.add_resource(SkimMatrixLayerStatus, "/skim/status/<string:identifier>")
api.add_resource(SkimMatrixLayerCalculate, "/skim/calculate/<string:identifier>/<string:attribute>/<string:metric>")
api.add_resource(SkimMatrixLayerData, "/skim/data/<string:identifier>")
api.add_resource(SkimMatrixLayerShape, "/skim/shape/<string:identifier>")

if __name__ == '__main__':
app.run(debug = True, host = '0.0.0.0')
29 changes: 24 additions & 5 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@
</b-navbar>
<b-row align-v="stretch" style="height: 100%;">
<b-col id="panel" cols="3" style="padding-top: 60px;">
<ZoneLayerPanel :layerState="layerState" />
<ZoneLayerPanel :layerState="layerState" v-if="false" />
<RequestsLayerPanel :layerState="requestsLayerState" v-if="false" />
<ActivitiesLayerPanel :layerState="activitiesLayerState" />
<ActivitiesLayerPanel :layerState="activitiesLayerState" v-if="false" />
<SkimMatrixLayerPanel :layerState="skimLayerState" />
</b-col>
<b-col>
<svg id="map" v-on:wheel="onScale" v-on:mousedown="onMouseDown" v-on:mouseup="onMouseUp" v-on:mouseover="onMouseMove" >
<ZoneLayer :layerState="layerState" />
<ZoneLayer :layerState="layerState" v-if="false" />
<RequestsLayer :layerState="requestsLayerState" v-if="false" />
<ActivitiesLayer :layerState="activitiesLayerState" />
<ActivitiesLayer :layerState="activitiesLayerState" v-if="false" />
<SkimMatrixLayer :layerState="skimLayerState" />
</svg>
</b-col>
</b-row>
Expand All @@ -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({
Expand All @@ -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
};
Expand Down
132 changes: 132 additions & 0 deletions src/components/SkimMatrixLayer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<template>
<g class="layer">
<path
v-for="zone in zones"
v-bind:key="zone.id"
v-bind:d="zone.geometry"
v-on:mouseenter="onEnter(zone)"
v-on:mouseleave="onLeave(zone)"
v-on:click="onClick(zone)"
v-bind:style="{ fill: zone.color }"
/>
</g>
</template>

<script>
import * as d3 from "d3";
export default {
name: "ZoneLayer",
data() {
return {
hoverZone: undefined, clickZone: undefined,
width: 0, height: 0,
hoverSelectedMunicipalityId: undefined,
clickSelectedMunicipalityId: undefined
};
},
props: ["layerState"],
computed: {
zones() {
var scale = this.layerState.scale;
var width = this.width;
var height = this.height;
var offset = this.layerState.offset;
var projection = d3.geoTransform({
point: function(x, y) {
this.stream.point(
(x - 651791.0) * scale + width * 0.5 + offset[0],
(y - 6862293.0) * scale + height * 0.5 + offset[1]
);
}
});
var generator = d3.geoPath()
.projection(projection);
var baseColor = d3.color("steelblue");
var selectedMunicipalityId = this.hoverSelectedMunicipalityId;
if (selectedMunicipalityId == undefined) {
selectedMunicipalityId = this.clickSelectedMunicipalityId;
}
return this.layerState.features.map((item) => {
var color = d3.color(baseColor);
color.opacity = 0.0;
if (selectedMunicipalityId != undefined) {
var selectedValues = this.layerState.data[selectedMunicipalityId];
if (selectedValues != undefined) {
var value = selectedValues[item.properties.municipality_id];
if (value != undefined) {
color.opacity = (value - this.layerState.minimumValue) / (this.layerState.maximumValue - this.layerState.minimumValue)
}
}
}
/*color.opacity = (item.properties.value - this.layerState.minimumValue) / (this.layerState.maximumValue - this.layerState.minimumValue)
color.opacity = 0.0;*/
return { id: item.properties.municipality_id, color: color, geometry: generator(item) };
});
}
},
methods: {
onClick(zone) {
if (this.clickedElement != undefined) {
this.clickedElement.classList.remove("selected");
}
this.clickSelectedMunicipalityId = zone.id;
this.clickedElement = event.target;
this.clickedElement.classList.add("selected");
},
onEnter(zone) {
this.hoverSelectedMunicipalityId = zone.id;
this.hoverZone = zone;
event.target.parentNode.appendChild(event.target);
event.target.classList.add("hover")
this.$emit("hoverZone", zone);
if (this.clickedElement != undefined) {
this.clickedElement.parentNode.appendChild(this.clickedElement);
}
},
onLeave() {
this.hoverSelectedMunicipalityId = undefined;
this.hoverZone = undefined;
event.target.classList.remove("hover")
this.$emit("hoverZone", undefined);
},
onResize() {
this.width = this.$el.parentNode.clientWidth;
this.height = this.$el.parentNode.clientHeight;
}
},
mounted() {
this.onResize();
window.addEventListener('resize', this.onResize);
}
}
</script>

<style scoped>
path {
stroke: #cccccc;
fill: white;
}
path.selected {
stroke: blue;
stroke-width: 2;
}
path.hover {
stroke: red;
}
</style>
Loading

0 comments on commit f76cf92

Please sign in to comment.