|
@@ -0,0 +1,180 @@
|
|
|
+import UnityEngine as ue
|
|
|
+import numpy as np
|
|
|
+import seaborn as sns
|
|
|
+import pandas as pd
|
|
|
+import matplotlib.pyplot as plt
|
|
|
+import matplotlib.patches as patches
|
|
|
+from matplotlib.colors import LinearSegmentedColormap
|
|
|
+from matplotlib.tri import Triangulation
|
|
|
+
|
|
|
+WIDTH = int(70)
|
|
|
+HEIGHT = int(35)
|
|
|
+OBSTACLE_PATH1 = "Assets/Data_image/obstacle1.pkl"
|
|
|
+OBSTACLE_PATH2 = "Assets/Data_image/obstacle2.pkl"
|
|
|
+OBSTACLE_PATH3 = "Assets/Data_image/obstacle3.pkl"
|
|
|
+POSITION_PATH1 = ue.Application.dataPath + '/Data_position/Walk1.csv'
|
|
|
+POSITION_PATH2 = ue.Application.dataPath + '/Data_position/Walk2.csv'
|
|
|
+POSITION_PATH3 = ue.Application.dataPath + '/Data_position/Walk3.csv'
|
|
|
+HEATMAP_PATH = "Assets/Data_image/heatmapMultiple1.png"
|
|
|
+
|
|
|
+# Generate only if obstacles change
|
|
|
+def set_obstacles(maximum, path):
|
|
|
+ """
|
|
|
+ set_obstacles creates a DataFrame which marks the positions of the obstacles.
|
|
|
+ The obstacles are marked with the negative maximum and the file is placed at the path.
|
|
|
+ The negative maximum is used so that the cmap can assign the correct colors to the values.
|
|
|
+ Since no other negative numbers exist and the next highest number is 0, the negative values are painted 'grey'
|
|
|
+ and the other [0-maximum] are painted according to the color gradient between the two specified colors.
|
|
|
+
|
|
|
+ :param maximum: Is the maximum to the associated positions file
|
|
|
+ :param path: Is the location of the file created in this function
|
|
|
+ """
|
|
|
+ global list_obstacle_length
|
|
|
+ positions = pd.DataFrame(np.zeros((HEIGHT, WIDTH)))
|
|
|
+ obstacles = ue.Object.FindObjectsOfType(ue.GameObject)
|
|
|
+ for obstacle in obstacles:
|
|
|
+ if(obstacle.layer == 15 or obstacle.layer == 12):
|
|
|
+ startWidth = int(obstacle.transform.position.x - obstacle.transform.localScale.x / 2)
|
|
|
+ endWidth = int(obstacle.transform.position.x + obstacle.transform.localScale.x / 2)
|
|
|
+ startHeight = int(obstacle.transform.position.z - obstacle.transform.localScale.z / 2)
|
|
|
+ endHeight = int(obstacle.transform.position.z + obstacle.transform.localScale.z / 2)
|
|
|
+ for currentW in range(startWidth, endWidth, 1):
|
|
|
+ for currentH in range(startHeight, endHeight, 1):
|
|
|
+ positions[currentW][currentH] = -maximum
|
|
|
+ positions.to_pickle(path)
|
|
|
+
|
|
|
+def set_patches(plt):
|
|
|
+ """
|
|
|
+ set_patches uses the positions of the obstacles to draw a black border around them.
|
|
|
+
|
|
|
+ :param plt: The plot, where the figure is placed, so it can be modified by this function
|
|
|
+ """
|
|
|
+ obstacles = ue.Object.FindObjectsOfType(ue.GameObject)
|
|
|
+ for obstacle in obstacles:
|
|
|
+ if(obstacle.layer == 15):
|
|
|
+ startWidth = int(obstacle.transform.position.x - obstacle.transform.localScale.x / 2)
|
|
|
+ endWidth = int(obstacle.transform.position.x + obstacle.transform.localScale.x / 2)
|
|
|
+ startHeight = int(obstacle.transform.position.z - obstacle.transform.localScale.z / 2)
|
|
|
+ endHeight = int(obstacle.transform.position.z + obstacle.transform.localScale.z / 2)
|
|
|
+ # plt.gca().add_patch(
|
|
|
+ plt.add_patch(
|
|
|
+ patches.Rectangle(
|
|
|
+ (startWidth, startHeight),
|
|
|
+ endWidth - startWidth,
|
|
|
+ endHeight - startHeight,
|
|
|
+ fill=False,
|
|
|
+ color='black'
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+def triangulation_for_triheatmap(M, N):
|
|
|
+ """
|
|
|
+ triangulation_for_triheatmap divides each pixel of the heatmap into 4 triangles of equal size.
|
|
|
+ For this it first determines all x and y coordinates of the vertices of all triangles.
|
|
|
+ Then it creates the triangles based on the x and y coordinates.
|
|
|
+
|
|
|
+ :param M: The width of the Heatmap
|
|
|
+ :param N: The height of the Heatmap
|
|
|
+ :return: The created triangles
|
|
|
+ """
|
|
|
+ xv, yv = np.meshgrid(np.arange(0, M+1), np.arange(0, N+1)) # vertices of the little squares
|
|
|
+ xc, yc = np.meshgrid(np.arange(0.5, M), np.arange(0.5, N)) # centers of the little squares
|
|
|
+ x = np.concatenate([xv.ravel(), xc.ravel()])
|
|
|
+ y = np.concatenate([yv.ravel(), yc.ravel()])
|
|
|
+ cstart = (M + 1) * (N + 1) # indices of the centers
|
|
|
+
|
|
|
+ trianglesN = [(i + 1 + (j + 1) * (M + 1), i + (j + 1) * (M + 1), cstart + i + j * M)
|
|
|
+ for j in range(N) for i in range(M)]
|
|
|
+ trianglesE = [(i + 1 + j * (M + 1), i + 1 + (j + 1) * (M + 1), cstart + i + j * M)
|
|
|
+ for j in range(N) for i in range(M)]
|
|
|
+ trianglesS = [(i + j * (M + 1), i + 1 + j * (M + 1), cstart + i + j * M)
|
|
|
+ for j in range(N) for i in range(M)]
|
|
|
+ trianglesW = [(i + (j + 1) * (M + 1), i + j * (M + 1), cstart + i + j * M)
|
|
|
+ for j in range(N) for i in range(M)]
|
|
|
+
|
|
|
+ return [Triangulation(x, y, triangles) for triangles in [trianglesN, trianglesE, trianglesS, trianglesW]]
|
|
|
+
|
|
|
+# 1. Get position data from csv file
|
|
|
+data1 = pd.read_csv(POSITION_PATH1, sep=';', usecols=["Position x", "Position z"], decimal=',', dtype={'Position x': float, 'Position z': float})
|
|
|
+data1 = data1.round(0)
|
|
|
+data2 = pd.read_csv(POSITION_PATH2, sep=';', usecols=["Position x", "Position z"], decimal=',', dtype={'Position x': float, 'Position z': float})
|
|
|
+data2 = data2.round(0)
|
|
|
+data3 = pd.read_csv(POSITION_PATH3, sep=';', usecols=["Position x", "Position z"], decimal=',', dtype={'Position x': float, 'Position z': float})
|
|
|
+data3 = data3.round(0)
|
|
|
+
|
|
|
+# 2. Group by positions and count appearance
|
|
|
+data_count1 = data1.groupby(['Position x', 'Position z']).size().reset_index(name='counts')
|
|
|
+data_count2 = data2.groupby(['Position x', 'Position z']).size().reset_index(name='counts')
|
|
|
+data_count3 = data3.groupby(['Position x', 'Position z']).size().reset_index(name='counts')
|
|
|
+
|
|
|
+# 3. Create wide-form DataFrame for generating heatmap
|
|
|
+positions1 = data_count1.loc[:,:].reset_index().pivot(index='Position z', columns='Position x', values='counts')
|
|
|
+positions2 = data_count2.loc[:,:].reset_index().pivot(index='Position z', columns='Position x', values='counts')
|
|
|
+positions3 = data_count3.loc[:,:].reset_index().pivot(index='Position z', columns='Position x', values='counts')
|
|
|
+
|
|
|
+# 4. Fill missing values
|
|
|
+positions1.fillna(0, inplace=True)
|
|
|
+positions2.fillna(0, inplace=True)
|
|
|
+positions3.fillna(0, inplace=True)
|
|
|
+
|
|
|
+# 5. Reindex DataFrame (70,35) size of Surface; (70, 35) first x width then z height
|
|
|
+positions1 = positions1.reindex_axis(range(0, HEIGHT), axis=0, fill_value=0)
|
|
|
+positions1 = positions1.reindex_axis(range(0, WIDTH), axis=1, fill_value=0)
|
|
|
+positions2 = positions2.reindex_axis(range(0, HEIGHT), axis=0, fill_value=0)
|
|
|
+positions2 = positions2.reindex_axis(range(0, WIDTH), axis=1, fill_value=0)
|
|
|
+positions3 = positions3.reindex_axis(range(0, HEIGHT), axis=0, fill_value=0)
|
|
|
+positions3 = positions3.reindex_axis(range(0, WIDTH), axis=1, fill_value=0)
|
|
|
+
|
|
|
+# 6.1 Get maximum value of positions data
|
|
|
+max1 = pd.DataFrame(positions1).max().max() # Gets the maximum of each column and than the maximum of the maximums
|
|
|
+max2 = pd.DataFrame(positions2).max().max() # Gets the maximum of each column and than the maximum of the maximums
|
|
|
+max3 = pd.DataFrame(positions3).max().max() # Gets the maximum of each column and than the maximum of the maximums
|
|
|
+
|
|
|
+# 6.2 Get obstacles (obstacles, market stalls) and paste specific value in positions
|
|
|
+# Therefore save pkl file in folder and read from it afterwards
|
|
|
+set_obstacles(max1, OBSTACLE_PATH1)
|
|
|
+set_obstacles(max2, OBSTACLE_PATH2)
|
|
|
+set_obstacles(max3, OBSTACLE_PATH3)
|
|
|
+positions_obstacles = pd.read_pickle(OBSTACLE_PATH1)
|
|
|
+positions_heatmap1 = pd.read_pickle(OBSTACLE_PATH1)
|
|
|
+positions_heatmap2 = pd.read_pickle(OBSTACLE_PATH2)
|
|
|
+positions_heatmap3 = pd.read_pickle(OBSTACLE_PATH3)
|
|
|
+
|
|
|
+# 7 Merge positions data with obstacles data
|
|
|
+positions_heatmap1.where(positions_heatmap1 != 0, positions1, inplace=True)
|
|
|
+positions_heatmap2.where(positions_heatmap2 != 0, positions2, inplace=True)
|
|
|
+positions_heatmap3.where(positions_heatmap3 != 0, positions3, inplace=True)
|
|
|
+
|
|
|
+# 7.1 Debug Output
|
|
|
+# positions_heatmap1 = pd.DataFrame(positions_heatmap1)
|
|
|
+# positions_heatmap1.to_html('Assets/Data_image/positions_heatmap1.html')
|
|
|
+# positions_heatmap2 = pd.DataFrame(positions_heatmap2)
|
|
|
+# positions_heatmap2.to_html('Assets/Data_image/positions_heatmap2.html')
|
|
|
+# positions_heatmap3 = pd.DataFrame(positions_heatmap3)
|
|
|
+# positions_heatmap3.to_html('Assets/Data_image/positions_heatmap3.html')
|
|
|
+
|
|
|
+# 8.1 Divide the pixel in four equal triangles and plot heatmap
|
|
|
+values = [positions_heatmap1, positions_heatmap2, positions_obstacles, positions_heatmap3]
|
|
|
+triangul = triangulation_for_triheatmap(WIDTH, HEIGHT)
|
|
|
+cmaps = [LinearSegmentedColormap.from_list(name='2019', colors=['grey', (0.40,0.76,0.65), (0.11,0.62,0.47)]),
|
|
|
+ LinearSegmentedColormap.from_list(name='2020', colors=['grey', (0.99,0.55,0.38), (0.85,0.37,0.01)]),
|
|
|
+ LinearSegmentedColormap.from_list(name='2021', colors=['grey', (1, 1, 0.975)]),
|
|
|
+ LinearSegmentedColormap.from_list(name='2021', colors=['grey', (0.55,0.63,0.80), (0.46,0.44,0.70)])]
|
|
|
+fig, ax = plt.subplots()
|
|
|
+imgs = [ax.tripcolor(t, np.ravel(val), cmap=cmap)
|
|
|
+ for t, val, cmap in zip(triangul, values, cmaps)]
|
|
|
+
|
|
|
+# 8.2 Mark the Market stalls
|
|
|
+set_patches(ax)
|
|
|
+
|
|
|
+# 9.1 Set visual settings
|
|
|
+ax.tick_params(axis='both', which='both', bottom='off', labelbottom='off', left='off', labelleft='off')
|
|
|
+ax.margins(x=0, y=0)
|
|
|
+ax.set_aspect('equal', 'box') # square cells
|
|
|
+plt.tight_layout()
|
|
|
+
|
|
|
+# 9.2 Show Heatmap
|
|
|
+plt.show()
|
|
|
+
|
|
|
+# 10. Save Heatmap
|
|
|
+fig.savefig(HEATMAP_PATH, transparent=True)
|