├── .gitignore ├── LICENSE ├── README.md ├── picture ├── 1.PNG ├── 2.PNG ├── 3.PNG └── 4.PNG ├── test_data ├── 11111111111TW.json ├── test_data.json └── 参数.txt ├── traclus-api ├── app │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ ├── algorithm_api │ │ │ ├── __init__.py │ │ │ ├── base │ │ │ │ ├── __init__.py │ │ │ │ ├── coordinate.py │ │ │ │ ├── distance_functions.py │ │ │ │ ├── geometry.py │ │ │ │ ├── hooks.py │ │ │ │ ├── line_segment_averaging.py │ │ │ │ ├── linked_list.py │ │ │ │ ├── parameter_estimation.py │ │ │ │ ├── partitioning.py │ │ │ │ ├── processed_tragectory_connection.py │ │ │ │ ├── read_data_from_file.py │ │ │ │ ├── representative_line_finding.py │ │ │ │ ├── representative_trajectory_average_inputs.py │ │ │ │ ├── traclus_dbscan.py │ │ │ │ └── trajectory.py │ │ │ ├── geojson_parser.py │ │ │ ├── traclus_api.py │ │ │ └── utils_api.py │ │ └── test.py │ └── ext.py ├── manage.py └── requirements.txt └── traclus-app ├── .gitignore ├── README.md ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.css ├── App.js ├── App.test.js ├── components │ ├── AppHeader │ │ ├── AppHeader.css │ │ └── AppHeader.js │ ├── ArgsForm │ │ ├── ArgsForm.css │ │ └── ArgsForm.js │ └── Map │ │ ├── Map.css │ │ └── Map.js ├── index.css ├── index.js ├── logo.svg ├── reportWebVitals.js ├── setupTests.js └── utils │ ├── color.js │ └── event.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | /traclus-api/.idea/ 4 | # python 5 | *.py[cod] 6 | 7 | # dependencies 8 | /node_modules 9 | /.pnp 10 | .pnp.js 11 | 12 | # testing 13 | /coverage 14 | 15 | # production 16 | 17 | # misc 18 | .DS_Store 19 | .env.local 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Dan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 关于 2 | 3 | 基于Python 3.7实现了轨迹聚类算法,并基于Flask和React开发了一个包含前后端程序的DEMO。 4 | 5 | github: https://github.com/rend42/TRACLUS_IMPLEMENTATION 6 | 7 | ## 使用 8 | 9 | ### 后端程序 10 | 11 | 进入`traclus-api`目录下,执行`pip install -r requirements.txt`安装相关依赖 12 | 13 | 执行`python manage.py runserver`启动算法计算服务 14 | 15 | ### 前端 16 | 17 | 直接进入`traclus-app/build`目录下,浏览器打开`index.html`文件即可 18 | 19 | 或者在`traclus-app`目录下,执行`yarn`安装依赖,再执行`yarn build`打包应用,然后在`build`目录下找到`index.html`打开 20 | 21 | ### 测试数据 22 | 23 | 启动后端服务,并打开页面后,点击操作框文件上传按钮上传测试数据文件夹下的`test_data.json`文件,并在参数输入框依次输入`参数.txt`中的指定参数或自行指定,点击确定提交参数。 24 | 25 | 再依次点击计算与显示框下的按钮,可以在地图上查看计算结果。 26 | 27 | ### 测试图 28 | 29 | ![原始数据](.\picture\1.png) 30 | 31 | ![轨迹分段](.\picture\2.PNG) 32 | 33 | ![轨迹聚类](.\picture\3.PNG)![代表性轨迹](.\picture\4.PNG) 34 | 35 | ## THANKS 36 | 37 | https://github.com/apolcyn/traclus_impl 38 | 39 | https://github.com/luborliu/TraClusAlgorithm 40 | 41 | 本项目参考了以上两个项目的代码。 42 | -------------------------------------------------------------------------------- /picture/1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/42arch/traclus_implementation/9f4443f96a06bddf69854584bc2f53589956bc12/picture/1.PNG -------------------------------------------------------------------------------- /picture/2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/42arch/traclus_implementation/9f4443f96a06bddf69854584bc2f53589956bc12/picture/2.PNG -------------------------------------------------------------------------------- /picture/3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/42arch/traclus_implementation/9f4443f96a06bddf69854584bc2f53589956bc12/picture/3.PNG -------------------------------------------------------------------------------- /picture/4.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/42arch/traclus_implementation/9f4443f96a06bddf69854584bc2f53589956bc12/picture/4.PNG -------------------------------------------------------------------------------- /test_data/参数.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/42arch/traclus_implementation/9f4443f96a06bddf69854584bc2f53589956bc12/test_data/参数.txt -------------------------------------------------------------------------------- /traclus-api/app/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | from app.api import register_blueprint 4 | from app.ext import init_ext 5 | 6 | 7 | def create_app(): 8 | app = Flask(__name__) 9 | 10 | # app.config.from_object() 11 | 12 | init_ext(app) 13 | 14 | register_blueprint(app) 15 | 16 | return app 17 | -------------------------------------------------------------------------------- /traclus-api/app/api/__init__.py: -------------------------------------------------------------------------------- 1 | from app.api.algorithm_api import traclus 2 | from app.api.test import test 3 | 4 | 5 | def register_blueprint(app): 6 | app.register_blueprint(test) 7 | app.register_blueprint(traclus, url_prefix='/api') 8 | -------------------------------------------------------------------------------- /traclus-api/app/api/algorithm_api/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | from flask_restful import Api 3 | 4 | from app.api.algorithm_api.traclus_api import PartitionResource, ClusterResource, ReplineResource 5 | from app.api.algorithm_api.utils_api import FileResource 6 | 7 | traclus = Blueprint('traclus', __name__) 8 | traclus_api = Api(traclus) 9 | 10 | traclus_api.add_resource(FileResource, '/file') 11 | 12 | traclus_api.add_resource(PartitionResource, '/partitions') 13 | traclus_api.add_resource(ClusterResource, '/clusters') 14 | traclus_api.add_resource(ReplineResource, '/rep_lines') 15 | -------------------------------------------------------------------------------- /traclus-api/app/api/algorithm_api/base/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/42arch/traclus_implementation/9f4443f96a06bddf69854584bc2f53589956bc12/traclus-api/app/api/algorithm_api/base/__init__.py -------------------------------------------------------------------------------- /traclus-api/app/api/algorithm_api/base/coordinate.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | from .line_segment_averaging import get_representative_line_from_trajectory_line_segments 4 | from .partitioning import call_partition_trajectory, get_line_segment_from_points 5 | from .traclus_dbscan import BestAvailableClusterCandidateIndex, TrajectoryLineSegmentFactory, TrajectoryClusterFactory, \ 6 | dbscan 7 | 8 | 9 | def with_spikes_removed(trajectory): 10 | if len(trajectory) <= 2: 11 | return trajectory[:] 12 | 13 | spikes_removed = [trajectory[0]] 14 | cur_index = 1 15 | while cur_index < len(trajectory) - 1: 16 | if trajectory[cur_index - 1].distance_to(trajectory[cur_index + 1]) > 0.0: 17 | spikes_removed.append(trajectory[cur_index]) 18 | cur_index += 1 19 | spikes_removed.append(trajectory[cur_index]) 20 | return spikes_removed 21 | 22 | 23 | def clean_trajectories(point_iterable_list): 24 | """ 轨迹数据清理 """ 25 | cleaned_input = [] 26 | for traj in map(lambda l: with_spikes_removed(l), point_iterable_list): 27 | cleaned_traj = [] 28 | if len(traj) > 1: 29 | prev = traj[0] 30 | cleaned_traj.append(traj[0]) 31 | for pt in traj[1:]: 32 | if prev.distance_to(pt) > 0.0: 33 | cleaned_traj.append(pt) 34 | prev = pt 35 | if len(cleaned_traj) > 1: 36 | cleaned_input.append(cleaned_traj) 37 | return cleaned_input 38 | 39 | 40 | # 单独执行轨迹分段,返回分段结果 41 | def get_partitioning_result(point_iterable_list): 42 | cleaned_input = clean_trajectories(point_iterable_list=point_iterable_list) 43 | partitioning_result = get_all_trajectory_line_segments_iterable_from_all_points_iterable( 44 | point_iterable_list=cleaned_input) 45 | return partitioning_result 46 | 47 | 48 | # 单独执行轨迹聚类,返回分段结果和聚类结果(簇集合) 49 | def get_clustering_result(point_iterable_list, epsilon, min_neighbors): 50 | partitioning_result = get_partitioning_result(point_iterable_list) 51 | clusters = get_cluster_iterable_from_all_points_iterable(cluster_candidates=partitioning_result, epsilon=epsilon, 52 | min_neighbors=min_neighbors) 53 | return partitioning_result, clusters 54 | 55 | 56 | # 执行全部步骤,返回分段结果,聚类结果和代表轨迹 57 | def get_rep_line_result(point_iterable_list, epsilon, min_neighbors, min_num_trajectories_in_cluster, 58 | min_vertical_lines, 59 | min_prev_dist): 60 | partitioning_result, clusters = get_clustering_result(point_iterable_list=point_iterable_list, epsilon=epsilon, 61 | min_neighbors=min_neighbors) 62 | partitioning_result_origin = copy.deepcopy(partitioning_result) 63 | clusters_origin = copy.deepcopy(clusters) 64 | 65 | rep_lines = get_representative_lines_from_trajectory(clusters=clusters, 66 | min_num_trajectories_in_cluster=min_num_trajectories_in_cluster, 67 | min_vertical_lines=min_vertical_lines, 68 | min_prev_dist=min_prev_dist) 69 | return partitioning_result_origin, clusters_origin, rep_lines 70 | 71 | 72 | # 执行轨迹分段全部操作,返回代表性轨迹集合 73 | def run_traclus(point_iterable_list, epsilon, min_neighbors, min_num_trajectories_in_cluster, min_vertical_lines, 74 | min_prev_dist, clusters_hook): 75 | # return get_rep_line_result(point_iterable_list=point_iterable_list, epsilon=epsilon, min_neighbors=min_neighbors, 76 | # min_num_trajectories_in_cluster=min_num_trajectories_in_cluster, 77 | # min_vertical_lines=min_vertical_lines, 78 | # min_prev_dist=min_prev_dist, clusters_hook=None) 79 | cleaned_input = clean_trajectories(point_iterable_list=point_iterable_list) 80 | # 1. 计算分段后 的轨迹线段集合 81 | all_traj_segs_iter_from_all_points = get_all_trajectory_line_segments_iterable_from_all_points_iterable( 82 | point_iterable_list=cleaned_input) 83 | # 2. 对轨迹线段集合 计算, 得到轨迹簇 84 | clusters = get_cluster_iterable_from_all_points_iterable(all_traj_segs_iter_from_all_points, epsilon, min_neighbors) 85 | if clusters_hook: 86 | clusters_hook(clusters) 87 | 88 | # 3. 获得代表性轨迹 89 | rep_lines = get_representative_lines_from_trajectory(clusters=clusters, 90 | min_num_trajectories_in_cluster=min_num_trajectories_in_cluster, 91 | min_vertical_lines=min_vertical_lines, 92 | min_prev_dist=min_prev_dist) 93 | return rep_lines 94 | 95 | 96 | def get_cluster_iterable_from_all_points_iterable(cluster_candidates, epsilon, min_neighbors): 97 | # dbscan() 98 | line_seg_index = BestAvailableClusterCandidateIndex(candidates=cluster_candidates, epsilon=epsilon) 99 | clusters = dbscan(cluster_candidates_index=line_seg_index, min_neighbors=min_neighbors, 100 | cluster_factory=TrajectoryClusterFactory()) 101 | return clusters 102 | 103 | 104 | def get_representative_lines_from_trajectory(clusters, min_num_trajectories_in_cluster, min_vertical_lines, 105 | min_prev_dist): 106 | rep_lines = [] 107 | for traj_cluster in clusters: 108 | if traj_cluster.num_trajectories_contained() >= min_num_trajectories_in_cluster: 109 | rep_line = get_representative_line_from_trajectory_line_segments( 110 | trajectory_line_segments=traj_cluster.get_trajectory_line_segments(), 111 | min_vertical_lines=min_vertical_lines, 112 | min_prev_dist=min_prev_dist) 113 | rep_lines.append(rep_line) 114 | return rep_lines 115 | 116 | 117 | def get_all_trajectory_line_segments_iterable_from_all_points_iterable(point_iterable_list): 118 | """ 执行分段, 从点集合 的集合中获取全部 分段后的 轨迹线段 119 | :param point_iterable_list: 轨迹线 点集合(一条轨迹) 的 集合 120 | :return: list [TrajectoryLineSegment...] 121 | """ 122 | out = [] 123 | cur_trajectory_id = 0 124 | for point_trajectory in point_iterable_list: 125 | line_segments = get_one_trajectory_line_segments_from_points_iterable(point_iterable=point_trajectory, 126 | trajectory_id=cur_trajectory_id) 127 | temp = 0 128 | for traj_seg in line_segments: 129 | out.append(traj_seg) 130 | temp += 1 131 | if temp <= 0: 132 | raise Exception() 133 | cur_trajectory_id += 1 134 | return out 135 | 136 | 137 | def get_one_trajectory_line_segments_from_points_iterable(point_iterable, trajectory_id): 138 | """ 从一组原始点 集合中获取 一条分段后的轨迹 139 | :param point_iterable: 点列表 140 | :param trajectory_id: 141 | :return: TrajectoryLineSegment 142 | """ 143 | good_indices = call_partition_trajectory(point_iterable) 144 | good_point_iterable = filter_by_indices(good_indices=good_indices, vals=point_iterable) 145 | line_segs = consecutive_item_iterator_getter(item_iterable=good_point_iterable) 146 | 147 | def _create_traj_line_seg(line_seg): 148 | return TrajectoryLineSegmentFactory().new_trajectory_line_seg(line_segment=line_seg, 149 | trajectory_id=trajectory_id) 150 | 151 | return list(map(_create_traj_line_seg, line_segs)) 152 | 153 | 154 | # 根据下标过滤 155 | # 猜测是从分段的点下标集合中得到轨迹点 156 | def filter_by_indices(good_indices, vals): 157 | """ 从分段算法得到的下标集合中得到 对应的轨迹点集合 158 | :param good_indices: 下标集合 159 | :param vals: 原始点数据(未分段) 集合 160 | :return: 分段后的点集合 161 | """ 162 | vals_iter = iter(vals) 163 | good_indices_iter = iter(good_indices) 164 | out_vals = [] 165 | 166 | num_vals = 0 167 | for i in good_indices_iter: 168 | if i != 0: 169 | raise ValueError("the first index should be 0, but it was " + str(i)) # 起点必须为0下标 170 | else: 171 | for item in vals_iter: 172 | out_vals.append(item) 173 | break 174 | num_vals = 1 175 | break 176 | 177 | max_good_index = 0 178 | vals_cur_index = 1 179 | 180 | for i in good_indices_iter: 181 | max_good_index = i 182 | for item in vals_iter: 183 | num_vals += 1 184 | if vals_cur_index == i: 185 | vals_cur_index += 1 186 | out_vals.append(item) 187 | break 188 | else: 189 | vals_cur_index += 1 190 | for i in vals_iter: 191 | num_vals += 1 192 | if num_vals < 2: 193 | raise ValueError("list passed in is too short") 194 | # 分段下标集合最大下标一定是 点集合最后一个 195 | if max_good_index != num_vals - 1: 196 | raise ValueError("last index is " + str(max_good_index) + " but there were " + str(num_vals) + " vals") 197 | # print(max_good_index, num_vals) 198 | return out_vals 199 | 200 | 201 | def consecutive_item_iterator_getter(item_iterable): 202 | """ 从分段的点 集合中 得到连续的线段 203 | :param item_iterable: 分段后的点集合 204 | :return: 分段后的线段集合 205 | """ 206 | # get_line_segment_from_points 207 | out_vals = [] 208 | iterator = iter(item_iterable) 209 | last_item = None 210 | num_items = 0 211 | for item in iterator: 212 | num_items = 1 213 | last_item = item 214 | break 215 | if num_items == 0: 216 | raise ValueError("iterator doesn't have any values") 217 | 218 | for item in iterator: 219 | num_items += 1 220 | line_seg = get_line_segment_from_points(last_item, item) 221 | out_vals.append(line_seg) 222 | last_item = item 223 | 224 | if num_items < 2: 225 | raise ValueError("iterator didn't have at least two items") 226 | return out_vals 227 | -------------------------------------------------------------------------------- /traclus-api/app/api/algorithm_api/base/distance_functions.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | 4 | # 确定长短线段 5 | def determine_longer_and_shorter_lines(line_a, line_b): 6 | if line_a.length < line_b.length: 7 | return line_b, line_a 8 | else: 9 | return line_a, line_b 10 | 11 | 12 | def get_total_distance_function(perp_dist_func, angle_dist_func, parrallel_dist_func): 13 | def __dist_func(line_a, line_b, perp_func=perp_dist_func, angle_func=angle_dist_func, 14 | parr_func=parrallel_dist_func): 15 | return perp_func(line_a, line_b) + angle_func(line_a, line_b) + parr_func(line_a, line_b) 16 | 17 | return __dist_func 18 | 19 | 20 | # 计算两条线段间的垂直距离 21 | def perpendicular_distance(line_a, line_b): 22 | longer_line, shorter_line = determine_longer_and_shorter_lines(line_a, line_b) 23 | dist_a = shorter_line.start.distance_to_projection_on(longer_line) 24 | dist_b = shorter_line.end.distance_to_projection_on(longer_line) 25 | 26 | if dist_a == 0.0 and dist_b == 0.0: 27 | return 0.0 28 | 29 | return (dist_a * dist_a + dist_b * dist_b) / (dist_a + dist_b) 30 | 31 | 32 | def __perpendicular_distance(line_a, line_b): 33 | longer_line, shorter_line = determine_longer_and_shorter_lines(line_a, line_b) 34 | dist_a = longer_line.line.project(shorter_line.start).distance_to(shorter_line.start) 35 | dist_b = longer_line.line.project(shorter_line.end).distance_to(shorter_line.end) 36 | 37 | if dist_a == 0.0 and dist_b == 0.0: 38 | return 0.0 39 | else: 40 | return (math.pow(dist_a, 2) + math.pow(dist_b, 2)) / (dist_a + dist_b) 41 | 42 | 43 | # 计算两条线段的夹角距离 44 | def angular_distance(line_a, line_b): 45 | longer_line, shorter_line = determine_longer_and_shorter_lines(line_a, line_b) 46 | sine_coefficient = shorter_line.sine_of_angle_with(longer_line) 47 | return abs(sine_coefficient * shorter_line.length) 48 | 49 | 50 | # 两条线段水平距离 51 | def parrallel_distance(line_a, line_b): 52 | longer_line, shorter_line = determine_longer_and_shorter_lines(line_a, line_b) 53 | 54 | def __func(shorter_line_pt, longer_line_pt): 55 | return shorter_line_pt.distance_from_point_to_projection_on_line_seg(longer_line_pt, longer_line) 56 | 57 | return min([longer_line.dist_from_start_to_projection_of(shorter_line.start), 58 | longer_line.dist_from_start_to_projection_of(shorter_line.end), 59 | longer_line.dist_from_end_to_projection_of(shorter_line.start), 60 | longer_line.dist_from_end_to_projection_of(shorter_line.end)]) 61 | 62 | 63 | # 到投影点的距离 64 | def dist_to_projection_point(line, proj): 65 | return min(proj.distance_to(line.start), proj.distance_to(line.end)) 66 | -------------------------------------------------------------------------------- /traclus-api/app/api/algorithm_api/base/geometry.py: -------------------------------------------------------------------------------- 1 | import math 2 | from argparse import ArgumentError 3 | from .distance_functions import angular_distance, perpendicular_distance 4 | 5 | DECIMAL_MAX_DIFF_FOR_EQUALITY = 0.0000001 6 | 7 | _delta = 0.000000001 8 | 9 | 10 | def set_max_delta_for_equality(delta): 11 | _delta = delta 12 | 13 | 14 | # 向量类 15 | class Vector(object): 16 | def __init__(self, x, y): 17 | self.x = float(x) 18 | self.y = float(y) 19 | if x != 0.0: 20 | self.angle = math.degrees(math.atan(float(y) / x)) 21 | elif y == 0.0: 22 | self.angle = 0 23 | elif y > 0.0: 24 | self.angle = 90 25 | elif y < 0.0: 26 | self.angle = -90 27 | 28 | if self.x < 0: 29 | self.angle += 180 30 | 31 | # 两个向量的点积方法 32 | def dot_product_with(self, other_vector): 33 | return self.x * other_vector.x + self.y * other_vector.y 34 | 35 | # 字典格式 36 | def as_dict(self): 37 | return {'x': self.x, 'y': self.y} 38 | 39 | # 被矩阵相乘 40 | def multipled_by_matrix(self, x1, y1, x2, y2): 41 | new_x = self.x * x1 + self.y * x2 42 | new_y = self.x * y1 + self.y * y2 43 | return Vector(new_x, new_y) 44 | 45 | # 旋转 46 | def rotated(self, angle_in_degrees): 47 | cos_angle = math.cos(math.radians(angle_in_degrees)) 48 | sin_angle = math.sin(math.radians(angle_in_degrees)) 49 | return self.multipled_by_matrix(x1=cos_angle, y1=sin_angle, x2=-sin_angle, y2=cos_angle) 50 | 51 | # 判断相等 52 | def almost_equals(self, other): 53 | return abs(self.x - other.x) <= DECIMAL_MAX_DIFF_FOR_EQUALITY and abs( 54 | self.y - other.y) <= DECIMAL_MAX_DIFF_FOR_EQUALITY 55 | 56 | def __eq__(self, other): 57 | return other is not None and abs(self.x - other.x) < _delta and abs(self.y - other.y) < _delta 58 | 59 | def __ne__(self, other): 60 | return not self.__eq__(other) 61 | 62 | def __str__(self): 63 | return "x: " + str(self.x) + "| y:" + str(self.y) 64 | 65 | 66 | def distance(diff_x, diff_y): 67 | return math.sqrt(diff_x * diff_x + diff_y * diff_y) 68 | 69 | 70 | # 点类 71 | class Point(Vector): 72 | def __init__(self, x, y): 73 | Vector.__init__(self, x, y) 74 | 75 | # 两点间距离 76 | def distance_to(self, other_point): 77 | diff_x = other_point.x - self.x 78 | diff_y = other_point.y - self.y 79 | return math.sqrt(math.pow(diff_x, 2) + math.pow(diff_y, 2)) 80 | 81 | # # 两点间(经纬度)地理距离(米) 82 | # def geo_distance_to(self, other_point): 83 | # pass 84 | 85 | def distance_to_projection_on(self, line_segment): 86 | diff_x = self.x - line_segment.start.x 87 | diff_y = self.y - line_segment.start.y 88 | 89 | return abs(diff_x * line_segment.unit_vector.y - diff_y * line_segment.unit_vector.x) 90 | 91 | def rotated(self, angle_in_degrees): 92 | result = Vector.rotated(self, angle_in_degrees) 93 | return Point(result.x, result.y) 94 | 95 | 96 | class LineSegment: 97 | """ 线段类 包含两个点 """ 98 | @staticmethod 99 | def from_tuples(start, end): 100 | return LineSegment(Point(start[0], start[1]), Point(end[0], end[1])) 101 | 102 | def __init__(self, start, end): 103 | self.start = start 104 | self.end = end 105 | self.length = start.distance_to(end) 106 | 107 | if self.length > 0.0: 108 | unit_x = (end.x - start.x) / self.length 109 | unit_y = (end.y - start.y) / self.length 110 | self.unit_vector = Point(unit_x, unit_y) # 单位向量 111 | 112 | def as_dict(self): 113 | return {'start': self.start.as_dict(), 'end': self.end.as_dict()} 114 | 115 | # 与另一条线段的正弦角度 116 | def sine_of_angle_with(self, other_line_segment): 117 | return self.unit_vector.x * other_line_segment.unit_vector.y - self.unit_vector.y * other_line_segment.unit_vector.x 118 | 119 | # 从线段开始点到另一点投影的距离 120 | def dist_from_start_to_projection_of(self, point): 121 | diff_x = self.start.x - point.x 122 | diff_y = self.start.y - point.y 123 | return abs(diff_x * self.unit_vector.x + diff_y * self.unit_vector.y) 124 | 125 | # 从线段尾点到一个点投影点的距离 126 | def dist_from_end_to_projection_of(self, point): 127 | diff_x = self.end.x - point.x 128 | diff_y = self.end.y - point.y 129 | return abs(diff_x * self.unit_vector.x + diff_y * self.unit_vector.y) 130 | 131 | def almost_equals(self, other): 132 | return self.start.almost_equals(other.start) and self.end.almost_equals(other.end) 133 | 134 | def __eq__(self, other): 135 | return other is not None and (self.start == other.start and self.end == other.end) 136 | 137 | def __ne__(self, other): 138 | return not self.__eq__(other) 139 | 140 | def __str__(self): 141 | return "start: " + str(self.start) + " --- end: " + str(self.end) 142 | 143 | 144 | class Trajectory: 145 | """ 轨迹类 , 包含多个点集""" 146 | def __init__(self, id): 147 | self.points = [] 148 | self.id = id 149 | 150 | # 检查指标参数 151 | def check_indice_args(self, start, end): 152 | if start < 0 or start > len(self.points) - 2: 153 | raise ArgumentError('invalid start index') 154 | elif end <= start or end > len(self.points) - 1: 155 | raise ArgumentError('invalid end index') 156 | 157 | def model_cost(self, start, end): 158 | self.check_indice_args(start, end) 159 | return math.log(self.points[start].distance_to(self.points[end]), 2) 160 | 161 | def encoding_cost(self, start, end): 162 | self.check_indice_args(start, end) 163 | approximation_line = LineSegment(self.points[start], self.points[end]) 164 | 165 | total_perp = 0.0 166 | total_angular = 0.0 167 | for i in range(start, end): 168 | line_seg = LineSegment(self.points[i], self.points[i + 1]) 169 | total_perp += perpendicular_distance(approximation_line, line_seg) 170 | total_angular += angular_distance(approximation_line, line_seg) 171 | 172 | if total_perp < 1.0: 173 | total_perp = 1.0 174 | if total_angular < 1.0: 175 | total_angular = 1.0 176 | 177 | return math.log(total_perp, 2) + math.log(total_angular, 2) 178 | 179 | def get_partition(self): 180 | return range(0, len(self.points)) 181 | 182 | def __repr__(self): 183 | return str(self.points) 184 | -------------------------------------------------------------------------------- /traclus-api/app/api/algorithm_api/base/hooks.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def partitioned_points_hook(partitioned_points): 4 | pass 5 | 6 | 7 | def clusters_hook(clusters): 8 | pass 9 | -------------------------------------------------------------------------------- /traclus-api/app/api/algorithm_api/base/line_segment_averaging.py: -------------------------------------------------------------------------------- 1 | from .geometry import Point 2 | from .representative_line_finding import get_average_vector, get_rotated_line_segment 3 | from .representative_trajectory_average_inputs import get_representative_trajectory_average_inputs, \ 4 | DECIMAL_MAX_DIFF_FOR_EQUALITY 5 | 6 | 7 | def interpolate_within_line_segment(line_segment, horizontal_coordinate): 8 | """ 计算一个水平坐标x, 在一个线段上的垂直坐标 y 9 | :param line_segment: 10 | :param horizontal_coordinate: 11 | :return: 垂直坐标 12 | """ 13 | min_x = min(line_segment.start.x, line_segment.end.x) 14 | max_x = max(line_segment.start.x, line_segment.end.x) 15 | 16 | if not (min_x <= horizontal_coordinate + DECIMAL_MAX_DIFF_FOR_EQUALITY and max_x >= horizontal_coordinate - DECIMAL_MAX_DIFF_FOR_EQUALITY): 17 | raise Exception( 18 | "horizontal coordinate " + str(horizontal_coordinate) + " not within horizontal range of line segment" + 19 | " with bounds " + str(min_x) + " and" + str(max_x)) 20 | elif line_segment.start.y - line_segment.end.y == 0.0: 21 | return line_segment.start.y 22 | elif line_segment.start.x - line_segment.end.x == 0.0: 23 | return (line_segment.end.y - line_segment.start.y) / 2.0 + line_segment.start.y 24 | else: 25 | return float((horizontal_coordinate - line_segment.start.x)) / (line_segment.end.x - line_segment.start.x) * ( 26 | line_segment.end.y - line_segment.start.y) + line_segment.start.y 27 | 28 | 29 | def line_segment_averaging_set_iterable(line_segments_to_average): 30 | line_segment_averaging_set = [] 31 | horizontal_coord = line_segments_to_average['horizontal_position'] 32 | for seg in line_segments_to_average['lines']: 33 | line_segment_averaging_set.append({"horizontal_pos": horizontal_coord, "line_seg": seg.line_segment}) 34 | 35 | return line_segment_averaging_set 36 | 37 | 38 | def number_average(iter_ob, func): 39 | total = 0.0 40 | count = 0.0 41 | for item in iter_ob: 42 | total += func(item) 43 | count += 1 44 | 45 | if count == 0: 46 | raise Exception("no input given to take average of") 47 | else: 48 | return total / count 49 | 50 | 51 | def get_mean_vertical_coordinate_in_line_segment(line_segments_to_average): 52 | def apply_interpolation_to_line_segment(interpolation_info): 53 | if interpolation_info['line_seg'] is None or interpolation_info['horizontal_pos'] is None: 54 | raise Exception( 55 | "null key. " + str(interpolation_info) + "was passed to apply_interpolation_to_line_segment") 56 | return interpolate_within_line_segment(interpolation_info['line_seg'], interpolation_info['horizontal_pos']) 57 | 58 | line_segment_averaging_set = line_segment_averaging_set_iterable(line_segments_to_average) 59 | return number_average(line_segment_averaging_set, apply_interpolation_to_line_segment) 60 | 61 | 62 | def get_representative_line_from_rotated_line_segments(trajectory_line_segments, min_vertical_lines, min_prev_dist): 63 | inputs = get_representative_trajectory_average_inputs(trajectory_line_segments=trajectory_line_segments, 64 | min_prev_dist=min_prev_dist, 65 | min_lines=min_vertical_lines) 66 | out = [] 67 | for line_seg_averaging_input in inputs: 68 | vert_val = get_mean_vertical_coordinate_in_line_segment(line_seg_averaging_input) 69 | out.append(Point(line_seg_averaging_input["horizontal_position"], vert_val)) 70 | return out 71 | 72 | 73 | def get_representative_line_from_trajectory_line_segments(trajectory_line_segments, min_vertical_lines, min_prev_dist): 74 | average_trajectory_vector = get_average_vector( 75 | line_segment_list=list(map(lambda x: x.line_segment, trajectory_line_segments))) 76 | 77 | for traj_line_seg in trajectory_line_segments: 78 | traj_line_seg.line_segment = get_rotated_line_segment(traj_line_seg.line_segment, 79 | - average_trajectory_vector.angle) 80 | 81 | representative_points = get_representative_line_from_rotated_line_segments( 82 | trajectory_line_segments=trajectory_line_segments, 83 | min_vertical_lines=min_vertical_lines, 84 | min_prev_dist=min_prev_dist) 85 | return list(map(lambda x: x.rotated(angle_in_degrees=average_trajectory_vector.angle), representative_points)) 86 | -------------------------------------------------------------------------------- /traclus-api/app/api/algorithm_api/base/linked_list.py: -------------------------------------------------------------------------------- 1 | # 链表类(双向) 2 | class LinkedList: 3 | def __init__(self): 4 | self.head = LinkedListNode(None) 5 | self.head.next = self.head 6 | self.head.prev = self.head 7 | self.size = 0 8 | 9 | def __iter__(self): 10 | return LinkedListIter(self) 11 | 12 | def __len__(self): 13 | return self.size 14 | 15 | def __getitem__(self, index): 16 | if index >= self.size or index < 0: 17 | raise IndexError 18 | cur = self.head.next 19 | count = 0 20 | while count < index: 21 | cur = cur.next 22 | count += 1 23 | return cur.item 24 | 25 | def add_last(self, item): 26 | temp = LinkedListNode(item) 27 | self.add_last_node(temp) 28 | 29 | def add_first(self, item): 30 | temp = LinkedListNode(item) 31 | self.add_first_node(temp) 32 | 33 | # 获取第一个节点 34 | def get_first(self): 35 | if self.empty(): 36 | raise Exception("can't get item from empty list") 37 | return self.head.next.item 38 | 39 | # 获取最后一个节点 40 | def get_last(self): 41 | if self.empty(): 42 | raise Exception("can't get item from empty list") 43 | return self.head.prev.item 44 | 45 | def empty(self): 46 | return self.head == self.head.next 47 | 48 | # 向尾部添加节点 49 | def add_last_node(self, new_node): 50 | new_node.next = self.head 51 | new_node.prev = self.head.prev 52 | self.head.prev.next = new_node 53 | self.head.prev = new_node 54 | self.size += 1 55 | 56 | # 向头部添加节点 57 | def add_first_node(self, new_node): 58 | new_node.next = self.head.next 59 | new_node.prev = self.head 60 | self.head.next.prev = new_node 61 | self.head.next = new_node 62 | self.size += 1 63 | 64 | def remove_node(self, new_node): 65 | if self.size == 0: 66 | raise Exception('视图从一个空链表中删除节点') 67 | new_node.next.prev = new_node.prev 68 | new_node.prev.next = new_node.next 69 | self.size -= 1 70 | 71 | 72 | # 链表节点类 73 | class LinkedListNode: 74 | def __init__(self, item): 75 | self.item = item 76 | self.next = None 77 | self.prev = None 78 | 79 | 80 | # 链表迭代类 81 | class LinkedListIter: 82 | def __init__(self, list): 83 | self.list = list 84 | self.pos = list.head.next 85 | 86 | def __next__(self): 87 | if self.pos == self.list.head: 88 | raise StopIteration 89 | else: 90 | item = self.pos.item 91 | self.pos = self.pos.next 92 | return item 93 | -------------------------------------------------------------------------------- /traclus-api/app/api/algorithm_api/base/parameter_estimation.py: -------------------------------------------------------------------------------- 1 | import math 2 | import random 3 | from functools import reduce 4 | from simanneal import Annealer 5 | from .coordinate import get_rep_line_result 6 | 7 | 8 | def find_entropy(all_line_segs): 9 | def _get_neighbors(seg): 10 | return seg.get_num_neighbors() + 1 11 | 12 | sum_total_neighborhood_size = reduce(lambda x, y: x + y, map(_get_neighbors, all_line_segs)) 13 | 14 | def _probability_func(line_seg, sum_total_neighborhood_size): 15 | return _get_neighbors(line_seg) * 1.0 / sum_total_neighborhood_size 16 | 17 | def _single_entry_entropy(line_seg): 18 | prob_value = _probability_func(line_seg, sum_total_neighborhood_size) 19 | return prob_value * math.log(prob_value, 2) 20 | 21 | return -1 * reduce(lambda x, y: x + y, map(_single_entry_entropy, all_line_segs)) 22 | 23 | 24 | class TraclusSimulatedAnnealingState: 25 | def __init__(self, input_trajectories, epsilon): 26 | if epsilon < 0.0: 27 | raise ValueError("can't have a negative epsilon") 28 | 29 | Annealer.copy_strategy = 'method' 30 | self.input_trajectories = input_trajectories 31 | self.epsilon = epsilon 32 | self.entropy = None 33 | 34 | def get_epsilon(self): 35 | return self.epsilon 36 | 37 | def get_input_trajectories(self): 38 | return self.input_trajectories 39 | 40 | def get_entropy(self): 41 | if self.entropy is None: 42 | raise Exception() 43 | return self.entropy 44 | 45 | def compute_entropy(self, clusters): 46 | all_line_segs = [] 47 | for single_cluster in clusters: 48 | all_line_segs.extend(single_cluster.get_trajectory_line_segments()) 49 | self.entropy = find_entropy(all_line_segs=all_line_segs) 50 | 51 | def copy(self): 52 | return TraclusSimulatedAnnealingState(self.input_trajectories, self.epsilon) 53 | 54 | 55 | class TraclusSimulatedAnnealer(Annealer): 56 | def __init__(self, initial_state, max_epsilon_step_change): 57 | self.max_epsilon_step_change = max_epsilon_step_change 58 | Annealer.__init__(self, initial_state=initial_state) 59 | 60 | def move(self): 61 | new_epsilon = max(0.0, self.state.get_epsilon() + random.uniform(-self.max_epsilon_step_change, 62 | self.max_epsilon_step_change)) 63 | self.state = TraclusSimulatedAnnealingState(self.state.input_trajectories, new_epsilon) 64 | 65 | def energy(self): 66 | get_rep_line_result(point_iterable_list=self.state.get_input_trajectories(), 67 | epsilon=self.state.get_epsilon(), 68 | min_neighbors=0, 69 | min_num_trajectories_in_cluster=1, 70 | min_vertical_lines=100, 71 | min_prev_dist=100, 72 | clusters_hook=self.state.compute_entropy) 73 | return self.state.get_entropy() 74 | -------------------------------------------------------------------------------- /traclus-api/app/api/algorithm_api/base/partitioning.py: -------------------------------------------------------------------------------- 1 | # 轨迹分段 2 | # 采用MDL原则把,通过垂直距离与角度距离定义MDL(par)和MDL(no_par), 如果MDL(par)大于MDL(no_par),则在当前点的 3 | # 前一个点进行切分 4 | 5 | import math 6 | from .distance_functions import angular_distance, perpendicular_distance 7 | from .geometry import LineSegment 8 | 9 | DISTANCE_OFFSET = 0.0000000001 10 | 11 | 12 | def call_partition_trajectory(trajectory_point_list): 13 | """ 轨迹分段 14 | 与下不同,输入为点列表 15 | :param trajectory_point_list: 轨迹线 点 列表 16 | :return: 17 | """ 18 | if len(trajectory_point_list) < 2: 19 | raise ValueError 20 | 21 | # def encoding_cost_func(trajectory_line_segs, low, high, partition_line): 22 | # return encoding_cost(trajectory_line_segs, low, high, 23 | # partition_line=partition_line, 24 | # angular_dist_func=angular_distance, 25 | # perpendicular_dist_func=perpendicular_distance) 26 | # 27 | # def partition_cost_func(trajectory_line_segs, low, high): 28 | # return partition_cost(trajectory_line_segs, low, high, 29 | # model_cost_func=model_cost, 30 | # encoding_cost_func=encoding_cost) 31 | 32 | trajectory_line_segs = list(map(lambda i: LineSegment(trajectory_point_list[i], trajectory_point_list[i + 1]), 33 | range(0, len(trajectory_point_list) - 1))) 34 | return partition_trajectory(trajectory_line_segs=trajectory_line_segs) 35 | 36 | 37 | def partition_trajectory(trajectory_line_segs): 38 | """轨迹分段 39 | 切分依据:比较 MDL_par 和 MDL_no_par 的大小,如果 MDL_par 大于MDL_no_par 则在前一节进行切分 40 | :param trajectory_line_segs: 轨迹线线段列表 41 | :param partition_cost_func: MDL_par 42 | :param no_partition_cost_func: MDL_no_par 43 | :return: 切分点列表 44 | """ 45 | if len(trajectory_line_segs) < 1: 46 | raise ValueError 47 | low = 0 48 | partition_points = [0] 49 | last_pt = trajectory_line_segs[len(trajectory_line_segs) - 1].end 50 | trajectory_line_segs.append(LineSegment(last_pt, last_pt)) 51 | 52 | for high in range(2, len(trajectory_line_segs)): 53 | mdl_par = partition_cost(trajectory_line_segs, low, high) 54 | mdl_no_par = no_partition_cost(trajectory_line_segs, low, high) 55 | 56 | if trajectory_line_segs[high - 2].unit_vector.almost_equals(trajectory_line_segs[high - 1].unit_vector): 57 | continue 58 | elif trajectory_line_segs[high].start.almost_equals(trajectory_line_segs[low].start) or mdl_par > mdl_no_par: 59 | partition_points.append(high - 1) 60 | low = high - 1 61 | 62 | partition_points.append(len(trajectory_line_segs) - 1) 63 | return partition_points 64 | 65 | 66 | def partition_cost(trajectory_line_segs, low, high): 67 | """ MDL_par 轨迹分段开销 68 | MDL(cost) = L(H) + L(D|H) 69 | :param trajectory_line_segs: 70 | :param low: 71 | :param high: 72 | :return: 73 | """ 74 | if low >= high: 75 | raise IndexError 76 | partition_line = LineSegment(trajectory_line_segs[low].start, trajectory_line_segs[high].start) 77 | 78 | model_cost = model_cost_func(partition_line) 79 | encoding_cost = encoding_cost_func(trajectory_line_segs, low, high, partition_line) 80 | return model_cost + encoding_cost 81 | 82 | 83 | def no_partition_cost(trajectory_line_segs, low, high): 84 | """ 计算MDL_no_par 85 | :param trajectory_line_segs: 86 | :param low: 87 | :param high: 88 | :return: 89 | """ 90 | if low >= high: 91 | raise ValueError 92 | total = 0.0 93 | for line_seg in trajectory_line_segs[low:high]: 94 | total += math.log(line_seg.length, 2) 95 | return total 96 | 97 | 98 | def model_cost_func(partition_line): 99 | """ L(H): 描述压缩模型所需要的长度 100 | :param partition_line: 101 | :return: 102 | """ 103 | return math.log(partition_line.length, 2) 104 | 105 | 106 | def encoding_cost_func(trajectory_line_segs, low, high, partition_line): 107 | """ L(D|H): 描述利用压缩模型所编码的数据所需要的长度 108 | :param trajectory_line_segs: 109 | :param low: 110 | :param high: 111 | :param partition_line: 112 | :return: 113 | """ 114 | total_angular = 0.0 115 | total_perp = 0.0 116 | for line_seg in trajectory_line_segs[low:high]: 117 | total_angular += angular_distance(partition_line, line_seg) 118 | total_perp += perpendicular_distance(partition_line, line_seg) 119 | 120 | return math.log(total_angular + DISTANCE_OFFSET, 2) + math.log(total_perp + DISTANCE_OFFSET, 2) 121 | 122 | 123 | def get_line_segment_from_points(point_a, point_b): 124 | """ 从两点中得到一条线段 125 | :param point_a: Point() 126 | :param point_b: Point() 127 | :return: LineSegment() 128 | """ 129 | return LineSegment(point_a, point_b) 130 | 131 | 132 | def get_trajectory_line_segment_iterator_adapter(iterator_getter, get_line_segment_from_points_func): 133 | def _func(list, low, high, get_line_segment_from_points_func=get_line_segment_from_points): 134 | iterator_getter(list, low, high, get_line_segment_from_points_func) 135 | 136 | return _func 137 | 138 | 139 | def get_trajectory_line_segment_iterator(list, low, high, get_line_segment_from_points_func): 140 | """ 从list[low, high]中得到一条轨迹线 141 | :param list: 点列表 [Point(), ...] 142 | :param low: 起始位置 143 | :param high: 终止位置 144 | :param get_line_segment_from_points_func: 两点成线函数 145 | :return: 线段list,即轨迹线 146 | """ 147 | if high <= low: 148 | raise Exception('high must be greater than low index') 149 | 150 | line_segs = [] 151 | cur_pos = low 152 | while cur_pos < high: 153 | line_segs.append(get_line_segment_from_points_func(list[cur_pos], list[cur_pos + 1])) 154 | cur_pos += 1 155 | 156 | return line_segs 157 | -------------------------------------------------------------------------------- /traclus-api/app/api/algorithm_api/base/processed_tragectory_connection.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import math 3 | from heapq import heappush, heappop 4 | 5 | from .geometry import LineSegment 6 | 7 | max_distance_for_neighbors_in_different_trajectories = 1 8 | 9 | 10 | class FilteredTrajectory: 11 | def __init__(self, trajectory, id): 12 | self.id = id 13 | self.trajectory = trajectory 14 | 15 | 16 | class FilteredTrajectoryConnection: 17 | def __init__(self, start_pt, end_pt, start_traj_id, end_traj_id): 18 | self.start_pt = start_pt 19 | self.end_pt = end_pt 20 | self.start_traj_pt = start_traj_id 21 | self.end_traj_pt = end_traj_id 22 | 23 | 24 | class FilteredPointGrapgNode: 25 | def __init__(self, point, index, original_trajectory_id): 26 | self.point = point 27 | self.index = index 28 | self.original_trajectory_id = original_trajectory_id 29 | self.neighbor_indices = set() 30 | self.graph_component_id = None 31 | 32 | def add_neighbor(self, other_node): 33 | if other_node != self: 34 | self.neighbor_indices.add(other_node.index) 35 | other_node.neighbor_indices.add(self.index) 36 | 37 | def get_neighbor_indices(self): 38 | return self.neighbor_indices 39 | 40 | def get_original_trajectory_id(self): 41 | return self.original_trajectory_id 42 | 43 | 44 | # add_other_neighbors_func 45 | # find_other_neighbors_func 46 | def get_find_other_nearby_neighbor_func(max_distance): 47 | def _func(node_index, pt_graph): 48 | for temp_node in pt_graph: 49 | if temp_node.point.distance_to(pt_graph[node_index].point) <= max_distance: 50 | pt_graph[node_index].add_neighbor(temp_node) 51 | 52 | return _func 53 | 54 | 55 | def get_find_other_nearby_neighbor(node_index, pt_graph, max_distance): 56 | for temp_node in pt_graph: 57 | if temp_node.point.distance_to(pt_graph[node_index].point) <= max_distance: 58 | pt_graph[node_index].add_neighbor(temp_node) 59 | 60 | 61 | # find_other_neighbors_func 62 | def dummy_find_other_neighbors_func(pt_node, pt_graph): 63 | return [] 64 | 65 | 66 | # def build_point_graph(filtered_trajectories, add_other_neighbors_func=None): 67 | # cur_pt_index = 0 68 | # pt_graph = [] 69 | # 70 | # for traj in filtered_trajectories: 71 | # prev_pt_graph_node = None 72 | # for pt in traj.trajectory: 73 | # pt_graph.append(FilteredPointGrapgNode(pt, cur_pt_index, traj.id)) 74 | # if prev_pt_graph_node is None: 75 | # pt_graph[cur_pt_index].add_neighbor(prev_pt_graph_node) 76 | # if add_other_neighbors_func is None: 77 | # add_other_neighbors_func(node_index=cur_pt_index, pt_graph=pt_graph) 78 | # prev_pt_graph_node = pt_graph[cur_pt_index] 79 | # cur_pt_index += 1 80 | 81 | 82 | def build_point_graph(filtered_trajectories): 83 | cur_pt_index = 0 84 | pt_graph = [] 85 | 86 | for traj in filtered_trajectories: 87 | prev_pt_graph_node = None 88 | for pt in traj.trajectory: 89 | pt_graph.append(FilteredPointGrapgNode(pt, cur_pt_index, traj.id)) 90 | if prev_pt_graph_node is None: 91 | pt_graph[cur_pt_index].add_neighbor(prev_pt_graph_node) 92 | # if add_other_neighbors_func is None: 93 | # add_other_neighbors_func(node_index=cur_pt_index, pt_graph=pt_graph) 94 | get_find_other_nearby_neighbor(node_index=cur_pt_index, pt_graph=pt_graph, 95 | max_distance=max_distance_for_neighbors_in_different_trajectories) 96 | prev_pt_graph_node = pt_graph[cur_pt_index] 97 | cur_pt_index += 1 98 | 99 | 100 | def mark_all_in_same_component(pt_node, component_id, pt_graph, find_other_neighbors_func): 101 | node_queue = collections.deque() 102 | node_queue.append(pt_node) 103 | 104 | while len(node_queue) > 0: 105 | temp_node = node_queue.popleft() 106 | 107 | def queue_adder_func(neighbor_index): 108 | if pt_graph[neighbor_index].graph_component_id is None: 109 | pt_graph[neighbor_index].graph_component_id = component_id 110 | node_queue.append(pt_graph[neighbor_index]) 111 | elif pt_graph[neighbor_index].graph_component_id != component_id: 112 | raise Exception("graph edges should not be directional") 113 | 114 | for neighbor_index in temp_node.get_neighbor_indices(): 115 | queue_adder_func(neighbor_index) 116 | for neighbor_index in find_other_neighbors_func(pt_node=temp_node, pt_graph=pt_graph): 117 | queue_adder_func(neighbor_index) 118 | 119 | 120 | def compute_graph_component_ids(pt_graph, find_other_neighbors_func): 121 | next_component_id = 0 122 | for pt_node in pt_graph: 123 | if pt_node.graph_component_id is None: 124 | mark_all_in_same_component(pt_node, next_component_id, pt_graph, find_other_neighbors_func) 125 | next_component_id += 1 126 | 127 | 128 | def compute_shortest_path(start_node_index, end_node_index, pt_graph): 129 | """ 计算最短路径 130 | :param start_node_index: 131 | :param end_node_index: 132 | :param pt_graph: 133 | :return: 134 | """ 135 | 136 | def pt_pt_distance_for_shortest_path_finding(a_index, b_index, pt_graph): 137 | pt_a = pt_graph[a_index].point 138 | pt_b = pt_graph[b_index].point 139 | return math.pow(pt_a.x - pt_b.x, 2) + math.pow(pt_a.y, pt_b.y, 2) 140 | 141 | priority_queue = [] 142 | distances_from_start = [None] * len(pt_graph) 143 | distances_from_start[start_node_index] = 0.0 144 | back_edges = [None] * len(pt_graph) 145 | heappush(priority_queue, (0.0, start_node_index)) 146 | 147 | end_node_reached = False 148 | while len(priority_queue) > 0: 149 | temp_node_index = heappop(priority_queue)[1] 150 | if temp_node_index == end_node_index: 151 | end_node_reached = True 152 | break 153 | for neighbor_index in pt_graph[temp_node_index].get_neighbor_indices(): 154 | temp_dist = pt_pt_distance_for_shortest_path_finding(temp_node_index, neighbor_index, pt_graph) + \ 155 | distances_from_start[temp_node_index] 156 | if distances_from_start[neighbor_index] is None or temp_dist < distances_from_start[neighbor_index]: 157 | distances_from_start[neighbor_index] = temp_dist 158 | heappush(priority_queue, (temp_dist, neighbor_index)) 159 | back_edges[neighbor_index] = temp_node_index 160 | 161 | if not end_node_reached: 162 | return None, None 163 | 164 | cur_index = end_node_index 165 | shortest_path = [end_node_index] 166 | while cur_index != start_node_index: 167 | cur_index = back_edges[cur_index] 168 | shortest_path.append(cur_index) 169 | shortest_path.reverse() 170 | return shortest_path, distances_from_start[end_node_index] 171 | 172 | 173 | def find_nearest_points_to_point(point, pt_graph, max_dist): 174 | """ 找到所有 一个点的最大距离范围内的的所有点 175 | :param point: 176 | :param pt_graph: 177 | :param distance_func: 178 | :param max_dist: 179 | :return: 180 | """ 181 | nearby_points = [] 182 | for pt_node in pt_graph: 183 | # if distance_func(point, pt_node.point) < max_dist: 184 | if point.distance_to(pt_node.point) < max_dist: 185 | nearby_points.append(pt_node.index) 186 | return nearby_points 187 | 188 | 189 | def find_all_possible_connections(start_pt, end_pt, pt_graph, max_dist_to_existing_pt): 190 | # 寻找所有可能的连接 191 | near_start_indices = find_nearest_points_to_point(start_pt, pt_graph, max_dist_to_existing_pt) 192 | near_end_indices = find_nearest_points_to_point(end_pt, pt_graph, max_dist_to_existing_pt) 193 | possible_connections = [] 194 | for start_index in near_start_indices: 195 | for end_index in near_end_indices: 196 | if pt_graph[end_index].graph_component_id == pt_graph[start_index].graph_component_id: 197 | possible_connections.append((start_index, end_index)) 198 | return possible_connections 199 | 200 | 201 | def find_shortest_connection(start_pt, end_pt, pt_graph, max_dist_to_existing_pt): 202 | """ 寻找最短连接 203 | :param start_pt: 204 | :param end_pt: 205 | :param pt_graph: 206 | :param max_dist_to_existing_pt: 207 | :return: 208 | """ 209 | # def pt_pt_distance_func_for_finding_nearby_points(pt_a, pt_b): 210 | # return pt_a.distance_to(pt_b) 211 | 212 | # def pt_pt_distance_func_for_shortest_path_finding(a_index, b_index, pt_graph): 213 | # pt_a = pt_graph[a_index].point 214 | # pt_b = pt_graph[b_index].point 215 | # return math.pow(pt_a.x - pt_b.x, 2) + math.pow(pt_a.y, pt_b.y, 2) 216 | 217 | possible_connections = find_all_possible_connections(start_pt=start_pt, end_pt=end_pt, pt_graph=pt_graph, 218 | max_dist_to_existing_pt=max_dist_to_existing_pt) 219 | if len(possible_connections) == 0: 220 | return None, None 221 | 222 | shortest_connection = None 223 | for connection in possible_connections: 224 | temp_path, temp_dist = compute_shortest_path(start_node_index=connection[0], end_node_index=connection[1], 225 | pt_graph=pt_graph) 226 | if shortest_connection is None or temp_dist < shortest_connection[1]: 227 | shortest_connection = (temp_path, temp_dist) 228 | 229 | return list(map(lambda i: pt_graph[i].point, shortest_connection[0])), shortest_connection[1] 230 | 231 | 232 | def get_nearest_point_line_segs(traj, all_trajectories): 233 | """ 获得最近点的线段 234 | :param traj: 235 | :param all_trajectories: 236 | :return: 237 | """ 238 | 239 | def get_all_points_of_trajectory(trajectory): 240 | point_list = list(map(lambda x: x.start, list(map(lambda y: y.trajectory, all_trajectories)))) 241 | point_list.append(trajectory.end) 242 | return point_list 243 | 244 | def get_nearest_point_in_line_segs_to_point(point): 245 | min_dist_line_seg = None 246 | min_square_dist = None 247 | for other_traj in all_trajectories: 248 | if other_traj.id != traj.id: 249 | for other_pt in get_all_points_of_trajectory(other_traj): 250 | temp_square_dist = math.pow(other_pt.x - point.x, 2) + math.pow(other_pt.y - point.y, 2) 251 | if min_square_dist is None or temp_square_dist < min_square_dist: 252 | min_square_dist = temp_square_dist 253 | min_dist_line_seg = LineSegment(point, other_pt) 254 | return min_dist_line_seg 255 | 256 | nearest_to_start = get_nearest_point_in_line_segs_to_point(traj.trajectory[0].start) 257 | nearest_to_end = get_nearest_point_in_line_segs_to_point(traj.trajectory[len(traj) - 1].end) 258 | 259 | return list(map(lambda x: FilteredTrajectoryConnection(x), [nearest_to_start, nearest_to_end])) 260 | 261 | 262 | def find_nearest_points_to_all_trajectory_endpoints(filtered_trajectories): 263 | nearest_point_line_segments = [] 264 | 265 | for traj in filtered_trajectories: 266 | nearest_point_line_segments.extend(get_nearest_point_line_segs(traj, filtered_trajectories)) 267 | 268 | return nearest_point_line_segments 269 | -------------------------------------------------------------------------------- /traclus-api/app/api/algorithm_api/base/read_data_from_file.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from .geometry import Point 4 | from .partitioning import call_partition_trajectory 5 | 6 | data_str = '' 7 | with open('raw_campus_trajectories.txt', 'r') as f: 8 | data_str = f.read() 9 | 10 | trajectory_list = json.loads(data_str)['trajectories'] 11 | 12 | for index, trajectory in enumerate(trajectory_list): 13 | print('轨迹:', index) 14 | # trajectory_point_list = [] 15 | # for p in trajectory: 16 | # point = Point(p['x'], p['y']) 17 | # # print(point) 18 | # trajectory_point_list.append(point) 19 | # print(trajectory_point_list) 20 | # partition_list = call_partition_trajectory(trajectory_point_list) 21 | # print(trajectory_point_list[0]) 22 | point_list = [] 23 | for p in trajectory_list[30]: 24 | print(p) 25 | point = Point(p['x'], p['y']) 26 | point_list.append(point) 27 | 28 | rl = call_partition_trajectory(point_list) 29 | print(point_list) 30 | print(rl) 31 | 32 | 33 | print(trajectory_list[0]) -------------------------------------------------------------------------------- /traclus-api/app/api/algorithm_api/base/representative_line_finding.py: -------------------------------------------------------------------------------- 1 | from .geometry import Vector, LineSegment 2 | 3 | 4 | def get_average_vector(line_segment_list): 5 | """ 计算线段的平均走向 6 | 将所有向量相加并单位化 7 | :param line_segment_list: 8 | :return: 9 | """ 10 | 11 | if len(line_segment_list) < 1: 12 | raise Exception("tried to get average vector of an empty line segment list") 13 | 14 | total_x = 0.0 15 | total_y = 0.0 16 | for segment in line_segment_list: 17 | if segment.end.x < segment.start.x: 18 | total_x += segment.start.x - segment.end.x 19 | else: 20 | total_x += segment.end.x - segment.start.x 21 | total_y += segment.end.y - segment.start.y 22 | 23 | return Vector(total_x, total_y) 24 | 25 | 26 | def get_rotated_line_segment(line_segment, angle_in_degrees): 27 | """ 坐标系旋转 28 | 如果扫描直线与x轴所成角度不为90°的整数倍,则将坐标系旋转是x轴与平均走向平行 29 | :param line_segment: 30 | :param angle_in_degrees: 31 | :return: 32 | """ 33 | if angle_in_degrees > 90.0 or angle_in_degrees < -90.0: 34 | raise Exception("trying to rotate line segment by an illegal number of degrees: " + str(angle_in_degrees)) 35 | 36 | new_start = line_segment.start.rotated(angle_in_degrees) 37 | new_end = line_segment.end.rotated(angle_in_degrees) 38 | 39 | return LineSegment.from_tuples((new_start.x, new_start.y), (new_end.x, new_end.y)) 40 | -------------------------------------------------------------------------------- /traclus-api/app/api/algorithm_api/base/representative_trajectory_average_inputs.py: -------------------------------------------------------------------------------- 1 | from operator import attrgetter 2 | 3 | from .linked_list import LinkedListNode, LinkedList 4 | 5 | DECIMAL_MAX_DIFF_FOR_EQUALITY = 0.0000001 6 | 7 | 8 | class TrajectoryLineSegmentEndpoint: 9 | """ 10 | 轨迹线段端点(包括开始点和结束点) 类 11 | """ 12 | 13 | def __init__(self, horizontal_position, line_segment, line_segment_id, list_node): 14 | self.horizontal_position = horizontal_position # x坐标 15 | self.line_segment = line_segment # 所属线段 16 | self.line_segment_id = line_segment_id # 线段id 17 | self.list_node = list_node 18 | 19 | 20 | def get_sorted_line_seg_endpoint(trajectory_line_segments): 21 | """ 按照(旋转后的轴的)坐标(x) 排列起始点与结束点 22 | :param trajectory_line_segments: 轨迹线段集合 23 | :return: 轨迹线段端点 集合 24 | """ 25 | line_segment_endpoints = [] 26 | cur_id = 0 27 | 28 | for traj_segment in trajectory_line_segments: 29 | list_node = LinkedListNode(traj_segment) 30 | line_segment_endpoints.append(TrajectoryLineSegmentEndpoint(traj_segment.line_segment.start.x, 31 | traj_segment, cur_id, list_node)) 32 | line_segment_endpoints.append(TrajectoryLineSegmentEndpoint(traj_segment.line_segment.end.x, 33 | traj_segment, cur_id, list_node)) 34 | cur_id += 1 35 | return sorted(line_segment_endpoints, key=attrgetter('horizontal_position')) 36 | 37 | 38 | def numbers_within(a, b, max_diff): 39 | return abs(a - b) <= max_diff 40 | 41 | 42 | def possibly_append_to_active_list(active_list, out, prev_pos, min_prev_dist, min_lines): 43 | """ 加入到激活的列表 44 | :param active_list: 45 | :param out: 46 | :param prev_pos: 47 | :param min_prev_dist: 48 | :param min_lines: 49 | :return: 50 | """ 51 | if (len(out) == 0 or prev_pos - out[len(out) - 1]['horizontal_position'] >= min_prev_dist) and len( 52 | active_list) >= min_lines: 53 | temp = [] 54 | for line_seg in active_list: 55 | temp.append(line_seg) 56 | out.append({"lines": temp, "horizontal_position": prev_pos}) 57 | 58 | 59 | def line_segments_were_adjacent(trajectory_seg_a, trajectory_seg_b): 60 | """ 判断两条轨迹线段是否毗连 61 | :param trajectory_seg_a: 62 | :param trajectory_seg_b: 63 | :return: 64 | """ 65 | return trajectory_seg_a.trajectory_id == trajectory_seg_b.trajectory_id and abs( 66 | trajectory_seg_a.position_in_trajectory - trajectory_seg_b.position_in_trajectory) == 1 67 | 68 | 69 | def same_trajectory_line_segment_connects(seg, line_seg_endpoint_list): 70 | """ 判断是否是同一条轨迹线段 连接 71 | :param seg: 72 | :param line_seg_endpoint_list: 73 | :return: 74 | """ 75 | for other in line_seg_endpoint_list: 76 | if line_segments_were_adjacent(seg, other.line_segment): 77 | return True 78 | return False 79 | 80 | 81 | def remove_duplicate_points_from_adjacent_line_of_sane_trajectories(active_list, insert_list, delete_list): 82 | """ 移除同一轨迹上毗邻线上的重复的点 83 | :param active_list: 84 | :param insert_list: 85 | :param delete_list: 86 | :return: 87 | """ 88 | insertion_line_seg_set = set() 89 | for endpoint in insert_list: 90 | insertion_line_seg_set.add(endpoint.line_segment) 91 | 92 | deletion_keeper_list = [] 93 | for endpoint in delete_list: 94 | if (endpoint.line_segment not in insertion_line_seg_set) and same_trajectory_line_segment_connects( 95 | endpoint.lines, insert_list): 96 | active_list.remove_node(endpoint.list_node) 97 | else: 98 | deletion_keeper_list.append(endpoint) 99 | delete_list[:] = deletion_keeper_list 100 | 101 | 102 | def get_representative_trajectory_average_inputs(trajectory_line_segments, min_lines, min_prev_dist): 103 | """ 104 | :param trajectory_line_segments: 105 | :param min_lines: 106 | :param min_prev_dist: 107 | :return: 108 | """ 109 | cur_active = [False] * len(trajectory_line_segments) # 初始化一个trajectory_line_segments长度值个的Flase的列表 110 | active_list = LinkedList() 111 | insert_list = [] 112 | delete_list = [] 113 | out = [] 114 | 115 | line_segment_endpoints = get_sorted_line_seg_endpoint(trajectory_line_segments) 116 | 117 | i = 0 118 | while i < len(line_segment_endpoints): 119 | insert_list[:] = [] 120 | delete_list[:] = [] 121 | prev_pos = line_segment_endpoints[i].horizontal_position 122 | 123 | while i < len(line_segment_endpoints) and numbers_within(line_segment_endpoints[i].horizontal_position, 124 | prev_pos, DECIMAL_MAX_DIFF_FOR_EQUALITY): 125 | if not cur_active[line_segment_endpoints[i].line_segment_id]: 126 | insert_list.append(line_segment_endpoints[i]) 127 | cur_active[line_segment_endpoints[i].line_segment_id] = True 128 | elif cur_active[line_segment_endpoints[i].line_segment_id]: 129 | delete_list.append(line_segment_endpoints[i]) 130 | cur_active[line_segment_endpoints[i].line_segment_id] = False 131 | i += 1 132 | 133 | for line_seg_endpoint in insert_list: 134 | active_list.add_last_node(line_seg_endpoint.list_node) 135 | possibly_append_to_active_list(active_list, out, prev_pos, min_prev_dist, min_lines) 136 | for line_seg in delete_list: 137 | active_list.remove_node(line_seg.list_node) 138 | 139 | return out 140 | 141 | -------------------------------------------------------------------------------- /traclus-api/app/api/algorithm_api/base/traclus_dbscan.py: -------------------------------------------------------------------------------- 1 | from .distance_functions import perpendicular_distance, angular_distance, parrallel_distance 2 | import collections 3 | 4 | 5 | # generic_dbscan 6 | class ClusterCandidate: 7 | """ 候选簇类 被 TrajectoryLineSegment类 继承 """ 8 | def __init__(self): 9 | self.cluster = None 10 | self.__is_noise = False 11 | 12 | def is_classified(self): 13 | return self.__is_noise or self.cluster is not None 14 | 15 | def is_noise(self): 16 | return self.__is_noise 17 | 18 | def set_as_noise(self): 19 | self.__is_noise = True 20 | 21 | def assign_to_cluster(self, cluster): 22 | self.cluster = cluster 23 | self.__is_noise = False 24 | 25 | def distance_to_candidate(self, other_candidate): 26 | raise NotImplementedError() 27 | 28 | 29 | class ClusterCandidateIndex: 30 | """ 簇 索引 类 被 TrajectoryLineSegmentCandidateIndex 继承 """ 31 | def __init__(self, candidates, epsilon): 32 | self.candidates = candidates 33 | self.epsilon = epsilon 34 | 35 | def find_neighbors_of(self, cluster_candidate): 36 | neighbors = [] 37 | for item in self.candidates: 38 | if item != cluster_candidate and cluster_candidate.distance_to_candidate(item) <= self.epsilon: 39 | neighbors.append(item) 40 | return neighbors 41 | 42 | 43 | class Cluster: 44 | """簇类""" 45 | def __init__(self): 46 | self.members = [] 47 | self.members_set = set() 48 | 49 | def add_member(self, item): 50 | if item in self.members_set: 51 | raise Exception('item' + str(item) + 'already exists in this cluster') 52 | self.members_set.add(item) 53 | self.members.append(item) 54 | 55 | def __repr__(self): 56 | return str(self.members) 57 | 58 | 59 | class ClusterFactory: 60 | @staticmethod 61 | def new_cluster(self): 62 | return Cluster() 63 | 64 | 65 | def dbscan(cluster_candidates_index, min_neighbors, cluster_factory): 66 | """ 线段聚类算法 67 | 对于一条未分类的线段,计算其邻域判断是否为核心线段。若是, 68 | :param cluster_candidates_index: 候选簇 下标 69 | :param min_neighbors: 70 | :param cluster_factory: 71 | :return: 返回簇的集合 72 | """ 73 | clusters = [] 74 | item_queue = collections.deque() 75 | 76 | for item in cluster_candidates_index.candidates: 77 | if not item.is_classified(): 78 | neighbors = cluster_candidates_index.find_neighbors_of(item) 79 | # 计算每个簇的基数,若值大于阈值 80 | if len(neighbors) >= min_neighbors: 81 | cur_cluster = cluster_factory.new_cluster() # 当前簇 82 | 83 | cur_cluster.add_member(item) 84 | item.assign_to_cluster(cur_cluster) 85 | 86 | for other_item in neighbors: 87 | other_item.assign_to_cluster(cur_cluster) 88 | cur_cluster.add_member(other_item) 89 | item_queue.append(other_item) 90 | 91 | expand_cluster(item_queue, cur_cluster, min_neighbors, cluster_candidates_index) 92 | clusters.append(cur_cluster) 93 | else: 94 | item.set_as_noise() # 小于阈值,将该簇淘汰(设为噪声) 95 | return clusters 96 | 97 | 98 | def expand_cluster(item_queue, cluster, min_neighbors, cluster_candidates_index): 99 | """ 计算一个(线段)密度连接集 100 | 如果新加入的线段为被分类,则把其加入队列item_queue中做进一步扩展, 101 | 若新加入的线段不是核心线段,则不加入到队列中 102 | :param item_queue: 103 | :param cluster: 104 | :param min_neighbors: 105 | :param cluster_candidates_index: 106 | :return: 107 | """ 108 | 109 | while len(item_queue) > 0: 110 | item = item_queue.popleft() 111 | neighbors = cluster_candidates_index.find_neighbors_of(item) 112 | if len(neighbors) >= min_neighbors: 113 | for other_item in neighbors: 114 | if not other_item.is_classified(): 115 | item_queue.append(other_item) 116 | if other_item.is_noise() or not other_item.is_classified(): 117 | other_item.assign_to_cluster(cluster) 118 | cluster.add_member(other_item) 119 | 120 | 121 | # traclus_dbscan部分 122 | class TrajectoryLineSegmentFactory: 123 | def __init__(self): 124 | self.next_traj_line_seg_id = 0 125 | 126 | def new_trajectory_line_seg(self, line_segment, trajectory_id): 127 | if line_segment is None or trajectory_id is None or trajectory_id < 0: 128 | raise Exception('invalid arguments') 129 | next_id = self.next_traj_line_seg_id 130 | self.next_traj_line_seg_id += 1 131 | return TrajectoryLineSegment(line_segment=line_segment, trajectory_id=trajectory_id, id=next_id) 132 | 133 | 134 | class TrajectoryLineSegment(ClusterCandidate): 135 | """"轨迹线段类""" 136 | def __init__(self, line_segment, trajectory_id, position_in_trajectory=None, id=None): 137 | ClusterCandidate.__init__(self) 138 | if line_segment is None or trajectory_id < 0: 139 | raise Exception 140 | self.line_segment = line_segment 141 | self.trajectory_id = trajectory_id 142 | self.position_in_trajectory = position_in_trajectory 143 | self.num_neighbors = -1 144 | self.id = id 145 | 146 | # 获取邻域线段数量 147 | def get_num_neighbors(self): 148 | if self.num_neighbors == -1: 149 | raise Exception("haven't counted num neighbors yet") 150 | return self.num_neighbors 151 | 152 | # 设置邻域数量 153 | def set_num_neighbors(self, num_neighbors): 154 | if self.num_neighbors != -1 and self.num_neighbors != num_neighbors: 155 | raise Exception("neighbors count should never be changing") 156 | self.num_neighbors = num_neighbors 157 | 158 | # 到另一候选轨迹线段的距离 159 | def distance_to_candidate(self, other_candidate): 160 | if other_candidate is None or other_candidate.line_segment is None or self.line_segment is None: 161 | raise Exception() 162 | return perpendicular_distance(self.line_segment, other_candidate.line_segment) + angular_distance( 163 | self.line_segment, other_candidate.line_segment) + parrallel_distance(self.line_segment, 164 | other_candidate.line_segment) 165 | 166 | def __repr__(self): 167 | return str(self.line_segment) 168 | 169 | 170 | class TrajectoryLineSegmentCandidateIndex(ClusterCandidateIndex): 171 | def __init__(self, candidates, epsilon): 172 | ClusterCandidateIndex.__init__(self, candidates, epsilon) 173 | 174 | def find_neighbors_of(self, cluster_candidate): 175 | neighbors = ClusterCandidateIndex.find_neighbors_of(self, cluster_candidate) 176 | cluster_candidate.set_num_neighbors(len(neighbors)) 177 | return neighbors 178 | 179 | 180 | BestAvailableClusterCandidateIndex = TrajectoryLineSegmentCandidateIndex 181 | # class RtreeTrajectoryLineSegmentCandidateIndex(ClusterCandidateIndex): 182 | # pass 183 | 184 | 185 | class TrajectoryCluster(Cluster): 186 | """轨迹簇 类""" 187 | def __init__(self): 188 | Cluster.__init__(self) 189 | self.trajectories = set() 190 | self.trajectory_count = 0 191 | 192 | def add_member(self, item): 193 | Cluster.add_member(self, item) 194 | if not (item.trajectory_id in self.trajectories): 195 | self.trajectory_count += 1 196 | self.trajectories.add(item.trajectory_id) 197 | 198 | def num_trajectories_contained(self): 199 | return self.trajectory_count 200 | 201 | def get_trajectory_line_segments(self): 202 | return self.members 203 | 204 | 205 | class TrajectoryClusterFactory(ClusterFactory): 206 | def new_cluster(self): 207 | return TrajectoryCluster() 208 | -------------------------------------------------------------------------------- /traclus-api/app/api/algorithm_api/base/trajectory.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from .distance_functions import perpendicular_distance, angular_distance 4 | from .geometry import LineSegment, Point 5 | from argparse import ArgumentError 6 | 7 | 8 | # 轨迹类 9 | class Trajectory: 10 | def __init__(self, id): 11 | self.points = [] 12 | self.id = id 13 | 14 | # 检查指标参数 15 | def check_indice_args(self, start, end): 16 | if start < 0 or start > len(self.points) - 2: 17 | raise ArgumentError('invalid start index') 18 | elif end <= start or end > len(self.points) - 1: 19 | raise ArgumentError('invalid end index') 20 | 21 | def model_cost(self, start, end): 22 | self.check_indice_args(start, end) 23 | return math.log(self.points[start].distance_to(self.points[end]), 2) 24 | 25 | def encoding_cost(self, start, end): 26 | self.check_indice_args(start, end) 27 | approximation_line = LineSegment(self.points[start], self.points[end]) 28 | 29 | total_perp = 0.0 30 | total_angular = 0.0 31 | for i in range(start, end): 32 | line_seg = LineSegment(self.points[i], self.points[i + 1]) 33 | total_perp += perpendicular_distance(approximation_line, line_seg) 34 | total_angular += angular_distance(approximation_line, line_seg) 35 | 36 | if total_perp < 1.0: 37 | total_perp = 1.0 38 | if total_angular < 1.0: 39 | total_angular = 1.0 40 | 41 | return math.log(total_perp, 2) + math.log(total_angular, 2) 42 | 43 | def get_partition(self): 44 | return range(0, len(self.points)) 45 | 46 | def __repr__(self): 47 | return str(self.points) 48 | -------------------------------------------------------------------------------- /traclus-api/app/api/algorithm_api/geojson_parser.py: -------------------------------------------------------------------------------- 1 | """ 将geojosn数据格式转换成聚类分析的格式 """ 2 | 3 | import json 4 | 5 | from .base.geometry import Point 6 | from .base.parameter_estimation import TraclusSimulatedAnnealingState, TraclusSimulatedAnnealer 7 | 8 | 9 | def read_data_from_json_file(file_path): 10 | with open(file_path, 'r') as f: 11 | json_data = f.read() 12 | 13 | return json_data 14 | 15 | 16 | def save_data_to_file(file_name, data): 17 | with open(file_name, 'w') as f: 18 | f.write(data) 19 | 20 | 21 | def get_trajectories_from_data(data_dict): 22 | """ 23 | :param data_dict: 24 | :return: 轨迹列表[轨迹点列表] 25 | """ 26 | features = data_dict['features'] 27 | trajectories_list = [] 28 | for feature in features: 29 | geometry = feature['geometry'] 30 | trajectory_point_list = [] 31 | if geometry['type'] == 'LineString': 32 | coordinates = geometry['coordinates'] 33 | for coord in coordinates: 34 | point = Point(coord[0], coord[1]) 35 | trajectory_point_list.append(point) 36 | # print(point) 37 | else: 38 | pass 39 | trajectories_list.append(trajectory_point_list) 40 | return trajectories_list 41 | 42 | 43 | def transform_geojson_to_trajectories(json_data): 44 | dict_data = json.loads(json_data) 45 | features = dict_data['features'] 46 | point_iter_list = [] 47 | for feature in features: 48 | geometry = feature['geometry'] 49 | point_iter = [] 50 | if geometry['type'] == 'LineString': 51 | coordinates = geometry['coordinates'] 52 | for coord in coordinates: 53 | point = Point(coord[0], coord[1]) 54 | point_iter.append(point) 55 | else: 56 | pass 57 | point_iter_list.append(point_iter) 58 | return point_iter_list 59 | 60 | 61 | def transform_trajectories_to_geojson(data_format, data): 62 | geojson = {"type": "FeatureCollection", "features": []} 63 | 64 | # 点集集合 格式 (原始数据 point_iter_list) 65 | if data_format == "point_iter_list": 66 | for point_iter in data: 67 | geometry = {"type": "LineString", "coordinates": []} 68 | feature = {"type": "Feature", "properties": {}, "geometry": geometry} 69 | for point in point_iter: 70 | p = [point.x, point.y] 71 | geometry["coordinates"].append(p) 72 | geojson["features"].append(feature) 73 | # 线段集合格式 (分段结果) 74 | elif data_format == "line_segment_list": 75 | for traj_line in data: 76 | geometry = {"type": "LineString", "coordinates": []} 77 | feature = {"type": "Feature", "properties": {}, "geometry": geometry} 78 | point_start = [traj_line.line_segment.start.x, traj_line.line_segment.start.y] 79 | point_end = [traj_line.line_segment.end.x, traj_line.line_segment.end.y] 80 | feature["properties"] = {"trajectory_id": traj_line.trajectory_id} 81 | geometry["coordinates"] = [point_start, point_end] 82 | geojson["features"].append(feature) 83 | 84 | # 轨迹簇 集合格式 (聚类结果) 85 | elif data_format == "trajectory_cluster_list": 86 | for index, cluster in enumerate(data): 87 | for trajectory_line_segment in cluster.get_trajectory_line_segments(): 88 | geometry = {"type": "LineString", "coordinates": []} 89 | feature = {"type": "Feature", "properties": {}, "geometry": geometry} 90 | point_start = [trajectory_line_segment.line_segment.start.x, 91 | trajectory_line_segment.line_segment.start.y] 92 | point_end = [trajectory_line_segment.line_segment.end.x, 93 | trajectory_line_segment.line_segment.end.y] 94 | feature["properties"] = {"trajectory_id": trajectory_line_segment.trajectory_id, "cluster_id": index} 95 | geometry["coordinates"] = [point_start, point_end] 96 | geojson["features"].append(feature) 97 | else: 98 | raise ValueError("invalid data_format") 99 | 100 | return json.dumps(geojson) 101 | 102 | 103 | def read_campus_data(): 104 | """ 读取 校园测试数据 105 | :return: 轨迹点集合 集合 106 | """ 107 | with open('raw_campus_trajectories.txt', 'r') as f: 108 | data = json.loads(f.read()) 109 | trajectories = data['trajectories'] 110 | point_iter_list = [] 111 | for tra in trajectories: 112 | point_iter = [] 113 | for p in tra: 114 | point = Point(p['x'], p['y']) 115 | point_iter.append(point) 116 | point_iter_list.append(point_iter) 117 | return point_iter_list 118 | 119 | 120 | def simulate_annealing(input_trajectories): 121 | # input_trajectories = [[Point(0, 0), Point(0, 1)], [Point(2, 0), Point(2, 1)], [Point(3, 0), Point(3, 1)]] 122 | initial_state = TraclusSimulatedAnnealingState(input_trajectories=input_trajectories, epsilon=0.0) 123 | traclus_sim_anneal = TraclusSimulatedAnnealer(initial_state=initial_state, max_epsilon_step_change=0.000001) 124 | traclus_sim_anneal.updates = 0 125 | traclus_sim_anneal.steps = 20 126 | best_state, best_energy = traclus_sim_anneal.anneal() 127 | print(best_state.get_epsilon()) 128 | 129 | -------------------------------------------------------------------------------- /traclus-api/app/api/algorithm_api/traclus_api.py: -------------------------------------------------------------------------------- 1 | from flask import request, make_response 2 | from flask_restful import Resource, reqparse 3 | 4 | from app.api.algorithm_api.base.coordinate import get_rep_line_result, get_clustering_result, get_partitioning_result 5 | from app.api.algorithm_api.geojson_parser import transform_geojson_to_trajectories, transform_trajectories_to_geojson 6 | 7 | 8 | 9 | parse_base = reqparse.RequestParser() 10 | parse_base.add_argument('data', type=str, required=True, help='please input geojson string!') 11 | 12 | parse_partitioning = parse_base.copy() 13 | 14 | parse_clustring = parse_base.copy() 15 | parse_clustring.add_argument('epsilon', type=float, required=True, help='please input the epsilon argument') 16 | parse_clustring.add_argument('min_neighbors', type=int, required=True, help='please input the min_neighbors argument') 17 | 18 | parse_traclus = parse_clustring.copy() 19 | parse_traclus.add_argument('min_num_trajectories_in_cluster', type=int, required=True, 20 | help="please input the min_num_trajectories argument") 21 | parse_traclus.add_argument('min_vertical_lines', type=int, required=True, 22 | help="please input the min_vertical_line argument") 23 | parse_traclus.add_argument('min_prev_dist', type=float, required=True, help="please input the min_prev_dist argument") 24 | 25 | 26 | class PartitionResource(Resource): 27 | def get(self): 28 | pass 29 | 30 | def post(self): 31 | args_partitioning = parse_partitioning.parse_args() 32 | json_data = args_partitioning.get('data') 33 | trajectories_data = transform_geojson_to_trajectories(json_data) 34 | partition_result = get_partitioning_result(trajectories_data) 35 | partition_result_json = transform_trajectories_to_geojson(data_format="line_segment_list", 36 | data=partition_result) 37 | response = make_response(partition_result_json) 38 | response.headers["Access-Control-Allow-Origin"] = "http://127.0.0.1:3000" 39 | 40 | return partition_result_json 41 | 42 | 43 | class ClusterResource(Resource): 44 | def get(self): 45 | pass 46 | 47 | def post(self): 48 | args_clustering = parse_clustring.parse_args() 49 | json_data = args_clustering.get('data') 50 | epsilon = args_clustering.get('epsilon') 51 | min_neighbors = args_clustering.get('min_neighbors') 52 | trajectories_data = transform_geojson_to_trajectories(json_data) 53 | partition_result, clusters = get_clustering_result(point_iterable_list=trajectories_data, epsilon=epsilon, 54 | min_neighbors=min_neighbors) 55 | clusters_json = transform_trajectories_to_geojson(data_format="trajectory_cluster_list", data=clusters) 56 | 57 | return clusters_json 58 | 59 | 60 | class ReplineResource(Resource): 61 | def get(self): 62 | pass 63 | 64 | def post(self): 65 | args_traclus = parse_traclus.parse_args() 66 | json_data = args_traclus.get('data') 67 | epsilon = args_traclus.get('epsilon') 68 | min_neighbors = args_traclus.get('min_neighbors') 69 | min_num_trajs = args_traclus.get('min_num_trajectories_in_cluster') 70 | min_vert_lines = args_traclus.get('min_vertical_lines') 71 | min_prev_dist = args_traclus.get('min_prev_dist') 72 | trajectories_data = transform_geojson_to_trajectories(json_data) 73 | partition_res, clusters, replines = get_rep_line_result(point_iterable_list=trajectories_data, 74 | epsilon=epsilon, 75 | min_neighbors=min_neighbors, 76 | min_num_trajectories_in_cluster=min_num_trajs, 77 | min_vertical_lines=min_vert_lines, 78 | min_prev_dist=min_prev_dist) 79 | replines_json = transform_trajectories_to_geojson(data_format="point_iter_list", data=replines) 80 | return replines_json 81 | -------------------------------------------------------------------------------- /traclus-api/app/api/algorithm_api/utils_api.py: -------------------------------------------------------------------------------- 1 | from flask import request, make_response 2 | from flask_restful import Resource, reqparse 3 | from werkzeug.datastructures import FileStorage 4 | 5 | 6 | class FileResource(Resource): 7 | def post(self): 8 | parser = reqparse.RequestParser() 9 | parser.add_argument('file', type=FileStorage, location='files') 10 | args = parser.parse_args() 11 | file = args['file'] 12 | file_bytes = file.read() 13 | file_string = file_bytes.decode() 14 | res = { 15 | 'file_name': file.filename, 16 | 'content': file_string 17 | } 18 | 19 | return res, 201 20 | -------------------------------------------------------------------------------- /traclus-api/app/api/test.py: -------------------------------------------------------------------------------- 1 | from flask import request, Blueprint, jsonify 2 | 3 | test = Blueprint('test', __name__) 4 | 5 | 6 | @test.route('/', methods=['GET']) 7 | def index(): 8 | data = { 9 | 'name': 'traclus-api', 10 | 'msg': 'it works!' 11 | } 12 | return jsonify(data) 13 | -------------------------------------------------------------------------------- /traclus-api/app/ext.py: -------------------------------------------------------------------------------- 1 | from flask_cors import CORS 2 | 3 | cors = CORS() 4 | 5 | 6 | def init_ext(app): 7 | cors.init_app(app) 8 | -------------------------------------------------------------------------------- /traclus-api/manage.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_script import Manager 3 | from app import create_app 4 | 5 | # env = os.environ.get('flask_env') or 'default' 6 | app = create_app() 7 | 8 | manager = Manager(app) 9 | 10 | if __name__ == '__main__': 11 | manager.run() 12 | -------------------------------------------------------------------------------- /traclus-api/requirements.txt: -------------------------------------------------------------------------------- 1 | aniso8601==9.0.1 2 | click==7.1.2 3 | Flask==1.1.2 4 | Flask-Cors==3.0.10 5 | Flask-RESTful==0.3.8 6 | Flask-Script==2.0.6 7 | itsdangerous==1.1.0 8 | Jinja2==2.11.3 9 | MarkupSafe==1.1.1 10 | pytz==2021.1 11 | simanneal==0.5.0 12 | six==1.15.0 13 | Werkzeug==1.0.1 14 | -------------------------------------------------------------------------------- /traclus-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /traclus-app/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `yarn start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `yarn test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `yarn build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `yarn eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `yarn build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /traclus-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "traclus-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": ".", 6 | "dependencies": { 7 | "@ant-design/icons": "^4.6.2", 8 | "@testing-library/jest-dom": "^5.11.4", 9 | "@testing-library/react": "^11.1.0", 10 | "@testing-library/user-event": "^12.1.10", 11 | "antd": "^4.15.2", 12 | "axios": "^0.21.1", 13 | "esri-leaflet": "^3.0.1", 14 | "events": "^3.3.0", 15 | "leaflet": "^1.7.1", 16 | "react": "^17.0.2", 17 | "react-dom": "^17.0.2", 18 | "react-scripts": "4.0.3", 19 | "web-vitals": "^1.0.1" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject" 26 | }, 27 | "eslintConfig": { 28 | "extends": [ 29 | "react-app", 30 | "react-app/jest" 31 | ] 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /traclus-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/42arch/traclus_implementation/9f4443f96a06bddf69854584bc2f53589956bc12/traclus-app/public/favicon.ico -------------------------------------------------------------------------------- /traclus-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /traclus-app/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/42arch/traclus_implementation/9f4443f96a06bddf69854584bc2f53589956bc12/traclus-app/public/logo192.png -------------------------------------------------------------------------------- /traclus-app/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/42arch/traclus_implementation/9f4443f96a06bddf69854584bc2f53589956bc12/traclus-app/public/logo512.png -------------------------------------------------------------------------------- /traclus-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /traclus-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /traclus-app/src/App.css: -------------------------------------------------------------------------------- 1 | @import '~leaflet/dist/leaflet.css'; 2 | @import '~antd/dist/antd.css'; 3 | 4 | .app { 5 | width: 100%; 6 | height: 100%; 7 | } 8 | 9 | .header { 10 | height: 3.2rem; 11 | } 12 | 13 | .content { 14 | width: 100%; 15 | height: calc(100% - 3.2rem); 16 | /* display: flex; 17 | flex-direction: row; 18 | justify-content: space-between; 19 | align-items: center; */ 20 | } -------------------------------------------------------------------------------- /traclus-app/src/App.js: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | import { Layout } from "antd"; 3 | import AppHeader from "./components/AppHeader/AppHeader"; 4 | import Map from "./components/Map/Map"; 5 | import ArgsForm from './components/ArgsForm/ArgsForm' 6 | 7 | const { Header, Footer, Sider, Content } = Layout; 8 | 9 | 10 | function App() { 11 | return ( 12 |
13 |
14 | 15 |
16 |
17 | 18 | 19 |
20 |
21 | 22 | 23 | //
24 | //
25 | // logo 26 | //

27 | // Edit src/App.js and save to reload. 28 | //

29 | // 35 | // Learn React 36 | // 37 | //
38 | //
39 | ); 40 | } 41 | 42 | export default App; 43 | -------------------------------------------------------------------------------- /traclus-app/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /traclus-app/src/components/AppHeader/AppHeader.css: -------------------------------------------------------------------------------- 1 | .app-header { 2 | background-color: #595959; 3 | height: 100%; 4 | display: flex; 5 | flex-direction: row; 6 | justify-content: baseline; 7 | align-items: center; 8 | cursor: pointer; 9 | border-bottom: 1px solid #2f54eb; 10 | } 11 | span#title { 12 | font-size: 1.8rem; 13 | margin-left: 1rem; 14 | color: #fafafa; 15 | } -------------------------------------------------------------------------------- /traclus-app/src/components/AppHeader/AppHeader.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { FundFilled } from '@ant-design/icons'; 3 | import './AppHeader.css' 4 | 5 | class AppHeader extends React.Component { 6 | render() { 7 | return ( 8 |
9 | 10 | 轨迹聚类系统DEMO 11 |
12 | ) 13 | } 14 | } 15 | 16 | export default AppHeader -------------------------------------------------------------------------------- /traclus-app/src/components/ArgsForm/ArgsForm.css: -------------------------------------------------------------------------------- 1 | .app-form { 2 | z-index: 999; 3 | border-radius: 3px; 4 | position: absolute; 5 | top: 4rem; 6 | right: 1rem; 7 | width: 20rem; 8 | height: 70%; 9 | /* min-height: 45rem; */ 10 | background-color: #8c8c8c; 11 | display: flex; 12 | flex-direction: column; 13 | align-items: center; 14 | } 15 | .app-form #title { 16 | font-size: 1.2rem; 17 | text-align: center; 18 | margin-bottom: 0.3rem; 19 | } 20 | 21 | .form-panel { 22 | width: 100%; 23 | } 24 | 25 | .ant-form-item { 26 | margin-bottom: 0.5rem; 27 | } 28 | .ant-input-number { 29 | width: 12rem; 30 | } 31 | 32 | .calcButtons { 33 | display: flex; 34 | flex-direction: column; 35 | justify-content: space-between; 36 | align-items: center; 37 | height: 7rem; 38 | } -------------------------------------------------------------------------------- /traclus-app/src/components/ArgsForm/ArgsForm.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import axios from "axios" 3 | import { Form, Collapse, Upload, message, Button, Spin, InputNumber } from "antd" 4 | import { UploadOutlined } from '@ant-design/icons' 5 | import emitter from "../../utils/event"; 6 | import './ArgsForm.css' 7 | 8 | const { Panel } = Collapse 9 | 10 | const layout = { 11 | labelCol: { 12 | span: 6, 13 | }, 14 | wrapperCol: { 15 | span: 18, 16 | }, 17 | } 18 | 19 | const tailLayout = { 20 | wrapperCol: { 21 | offset: 8, 22 | span: 16, 23 | }, 24 | } 25 | 26 | const uploadProps = { 27 | name: 'file', 28 | accept: '.json', 29 | headers: { 30 | authorization: 'authorization-text', 31 | } 32 | } 33 | 34 | 35 | const validateMessages = { 36 | required: '${label} is required!', 37 | types: { 38 | email: '${label} is not a valid email!', 39 | number: '${label} is not a valid number!', 40 | }, 41 | number: { 42 | range: '${label} must be between ${min} and ${max}', 43 | }, 44 | } 45 | 46 | class ArgsForm extends React.Component { 47 | constructor(props) { 48 | super(props) 49 | this.state = { 50 | values: {}, 51 | algorithmParams: {}, 52 | uploadStatus: '', 53 | originData: null, 54 | loading: false 55 | } 56 | this.customRequest = this.customRequest.bind(this) 57 | this.onChange = this.onChange.bind(this) 58 | this.onFinish = this.onFinish.bind(this) 59 | this.isParamsNull = this.isParamsNull.bind(this) 60 | this.getPartitions = this.getPartitions.bind(this) 61 | this.getClusters = this.getClusters.bind(this) 62 | this.getRepLines = this.getRepLines.bind(this) 63 | } 64 | 65 | componentDidMount() { 66 | 67 | } 68 | 69 | componentWillUnmount() { 70 | 71 | } 72 | 73 | customRequest(info) { 74 | let config = { 75 | headers: { "Content-Type": "multipart/form-data" } 76 | } 77 | let param = new FormData() 78 | param.append('file', info.file) 79 | axios.post('http://127.0.0.1:5000/api/file', param, config).then(res => { 80 | if(res.status === 201) { 81 | try { 82 | let geoData = JSON.parse(res.data.content) 83 | this.setState({originData: geoData}) 84 | this.setState({uploadStatus: 'done'}) 85 | emitter.emit('createLayer', geoData) 86 | message.success('GeoJSON文件上传并解析成功!') 87 | } catch (error) { 88 | this.setState({uploadStatus: 'error'}) 89 | message.error('根据GeoJSON创建图层失败,请移除并检查数据!') 90 | } 91 | } else { 92 | this.setState({uploadStatus: 'error'}) 93 | } 94 | }) 95 | } 96 | 97 | onChange(e) { 98 | console.log(233, e) 99 | e.file.status = this.state.uploadStatus 100 | } 101 | onRemove(e) { 102 | emitter.emit('removeLayer') 103 | } 104 | 105 | onFinish(v) { 106 | console.log('Success:', v) 107 | message.success('参数提交成功!') 108 | // this.state.algorithmParams = v 109 | this.setState({algorithmParams: v}) 110 | } 111 | 112 | onFinishFailed(errorInfo){ 113 | console.log('Failed:', errorInfo); 114 | } 115 | 116 | isParamsNull() { 117 | if(!!this.state.originData && !!this.state.algorithmParams) { 118 | let alParams = this.state.algorithmParams 119 | if(alParams.epsilon && alParams.min_neighbors && alParams.min_num_trajs_in_cluster && alParams.min_prev_dist && alParams.min_vertical_lines) { 120 | return true 121 | } 122 | } else { 123 | message.warn('确认上传原始数据和参数') 124 | return false 125 | } 126 | } 127 | 128 | // 获取轨迹分段 129 | getPartitions() { 130 | this.setState({loading: true}) 131 | let flag = this.isParamsNull() 132 | if(flag) { 133 | let params = new FormData() 134 | params.append('data', JSON.stringify(this.state.originData)) 135 | axios.post('http://127.0.0.1:5000/api/partitions', params).then(res => { 136 | // console.log('part', res.data) 137 | emitter.emit('showPartition', JSON.parse(res.data)) 138 | this.setState({loading: false}) 139 | }) 140 | } 141 | } 142 | 143 | // 获取轨迹聚类 144 | getClusters() { 145 | this.setState({loading: true}) 146 | let flag = this.isParamsNull() 147 | if(flag) { 148 | let params = new FormData() 149 | params.append('data', JSON.stringify(this.state.originData)) 150 | params.append('epsilon', this.state.algorithmParams.epsilon) 151 | params.append('min_neighbors', this.state.algorithmParams.min_neighbors) 152 | axios.post('http://127.0.0.1:5000/api/clusters', params).then(res => { 153 | // console.log('cluster', res.data) 154 | emitter.emit('showCluster', JSON.parse(res.data)) 155 | this.setState({loading: false}) 156 | }) 157 | } 158 | } 159 | 160 | // 获取代表轨迹 161 | getRepLines() { 162 | this.setState({loading: true}) 163 | let flag = this.isParamsNull() 164 | if(flag) { 165 | let params = new FormData() 166 | params.append('data', JSON.stringify(this.state.originData)) 167 | params.append('epsilon', this.state.algorithmParams.epsilon) 168 | params.append('min_neighbors', this.state.algorithmParams.min_neighbors) 169 | params.append('min_num_trajectories_in_cluster', this.state.algorithmParams.min_num_trajs_in_cluster) 170 | params.append('min_vertical_lines', this.state.algorithmParams.min_vertical_lines) 171 | params.append('min_prev_dist', this.state.algorithmParams.min_prev_dist) 172 | axios.post('http://127.0.0.1:5000/api/rep_lines', params).then(res => { 173 | console.log(res.data) 174 | 175 | emitter.emit('showRepLine', JSON.parse(res.data)) 176 | this.setState({loading: false}) 177 | }) 178 | 179 | } 180 | } 181 | 182 | 183 | render() { 184 | return ( 185 |
186 | 轨迹聚类计算 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 |
195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 214 | 215 |
216 |
217 | 218 | 219 | 220 |
221 | 224 | 227 | 230 |
231 |
232 | 233 | 234 |
235 |
236 |
237 | ) 238 | } 239 | } 240 | 241 | export default ArgsForm -------------------------------------------------------------------------------- /traclus-app/src/components/Map/Map.css: -------------------------------------------------------------------------------- 1 | .app-map { 2 | width: 100%; 3 | height: 100%; 4 | /* background-color: burlywood; */ 5 | } -------------------------------------------------------------------------------- /traclus-app/src/components/Map/Map.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import './Map.css' 3 | import L from 'leaflet' 4 | import * as esri from 'esri-leaflet' 5 | import emitter from "../../utils/event"; 6 | import { getColorByIndex } from "../../utils/color"; 7 | 8 | class Map extends React.Component { 9 | constructor() { 10 | super() 11 | this.state = { 12 | 13 | } 14 | this.map = Object.create(null) 15 | this.opLayers = [] 16 | this.mapOptions = {maxZoom: 20, minZoom:3, center:[0, 0]} 17 | } 18 | 19 | componentDidMount() { 20 | this.initMap() 21 | this.eventEmitter = emitter.addListener('createLayer', data => { 22 | this.createGeoJsonLayer(data) 23 | }) 24 | 25 | this.showPartitionEvent = emitter.addListener('showPartition', data => { 26 | this.showPartitionData(data) 27 | }) 28 | 29 | this.showCLusterEvent = emitter.addListener('showCluster', data => { 30 | this.showClusterData(data) 31 | }) 32 | 33 | this.showRepLineEvent = emitter.addListener('showRepLine', data => { 34 | this.showRepLineData(data) 35 | }) 36 | 37 | this.eventEmitter2 = emitter.addListener('removeLayer', () => { 38 | this.removeOpLayers() 39 | }) 40 | } 41 | 42 | initMap() { 43 | console.log('L', L) 44 | this.map = L.map("map", this.mapOptions).setView([30, 120], 6) 45 | this.addBaseMap() 46 | } 47 | 48 | async addBaseMap() { 49 | let base = await esri.tiledMapLayer({ 50 | url: 'http://map.geoq.cn/ArcGIS/rest/services/ChinaOnlineCommunity/MapServer' 51 | }).addTo(this.map) 52 | 53 | let darkBlue = await esri.tiledMapLayer({ 54 | url: 'http://map.geoq.cn/arcgis/rest/services/ChinaOnlineStreetPurplishBlue/MapServer' 55 | }) 56 | 57 | let grey = await esri.tiledMapLayer({ 58 | url: 'http://map.geoq.cn/arcgis/rest/services/ChinaOnlineStreetGray/MapServer' 59 | }) 60 | 61 | let ocean = await esri.tiledMapLayer({ 62 | url: 'http://server.arcgisonline.com/arcgis/rest/services/Ocean/World_Ocean_Base/MapServer' 63 | }) 64 | let street = esri.basemapLayer('Streets') 65 | 66 | this.baseLayer = { 67 | '基础地图': base, 68 | '海洋地形': ocean, 69 | '暗色地图': darkBlue, 70 | '灰色地图': grey, 71 | '街道地图': street 72 | } 73 | L.control.layers(this.baseLayer, this.overLayer).setPosition('topleft').addTo(this.map) 74 | } 75 | 76 | createGeoJsonLayer(data) { 77 | this.removeOpLayers() 78 | console.log('create layer!', data) 79 | let opLayer = L.geoJSON(data, { 80 | style: function(feature) { 81 | return {color: '#096dd9', weight: 1, opacity: 0.8 } 82 | } 83 | }).addTo(this.map) 84 | 85 | this.map.fitBounds(opLayer.getBounds()) 86 | this.opLayers.push(opLayer) 87 | } 88 | 89 | showPartitionData(data) { 90 | this.removeOpLayers() 91 | let partitionLayer = L.geoJSON(data, {style: function(feature) { return { 92 | color: getColorByIndex(feature.properties.trajectory_id), 93 | 'weight': 1, 'opacity': 1 94 | }}}) 95 | partitionLayer.bindPopup(lyr => { 96 | return `

详细信息

97 |

trajectory_id: ${lyr.feature.properties.trajectory_id}

` 98 | }).addTo(this.map) 99 | this.opLayers.push(partitionLayer) 100 | } 101 | 102 | showClusterData(data) { 103 | console.log('显示cluster') 104 | this.removeOpLayers() 105 | let clusteLayer = L.geoJSON(data, {style: function(feature) { return { 106 | color: getColorByIndex(feature.properties.cluster_id), 107 | 'weight': 1, 'opacity': 1 108 | }}}) 109 | clusteLayer.bindPopup(lyr => { 110 | return `

详细信息

111 |

trajectory_id: ${lyr.feature.properties.trajectory_id}

112 |

cluster_id: ${lyr.feature.properties.cluster_id}

` 113 | }).addTo(this.map) 114 | this.opLayers.push(clusteLayer) 115 | } 116 | 117 | showRepLineData(data) { 118 | let style = {'color': 'yellow', 'weight': 2, 'opacity': 1} 119 | let repLineLayer = L.geoJSON(data, style) 120 | repLineLayer.bindPopup(lyr => { 121 | return `

代表轨迹

` 122 | }).addTo(this.map) 123 | this.opLayers.push(repLineLayer) 124 | } 125 | 126 | removeOpLayers() { 127 | this.opLayers.forEach(lyr => this.map.removeLayer(lyr)) 128 | } 129 | 130 | 131 | 132 | render() { 133 | return ( 134 |
135 |
136 | ) 137 | } 138 | } 139 | 140 | export default Map -------------------------------------------------------------------------------- /traclus-app/src/index.css: -------------------------------------------------------------------------------- 1 | /* body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } */ 14 | 15 | html, body { 16 | width: 100%; 17 | height: 100%; 18 | padding: 0; 19 | margin: 0; 20 | } 21 | 22 | #root { 23 | width: 100%; 24 | height: 100%; 25 | } -------------------------------------------------------------------------------- /traclus-app/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | ReactDOM.render( 8 | // 9 | , 10 | // , 11 | document.getElementById('root') 12 | ); 13 | 14 | reportWebVitals(); 15 | -------------------------------------------------------------------------------- /traclus-app/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /traclus-app/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /traclus-app/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /traclus-app/src/utils/color.js: -------------------------------------------------------------------------------- 1 | function getColorByIndex(i){ 2 | if(i<10)i=i*302.3; 3 | if(i<100)i=i*31.2; 4 | for(;i>255;i*=0.98); 5 | var temp=i.toString().substring(i.toString().length-3); 6 | i+=parseInt(temp); 7 | for(;i>255;i-=255); 8 | i=parseInt(i); 9 | if(i<10)i+=10; 10 | 11 | var R=i*(i/100); 12 | for(;R>255;R-=255); 13 | if(R<50)R+=60; 14 | R=parseInt(R).toString(16); 15 | 16 | var G=i*(i%100); 17 | for(;G>255;G-=255); 18 | if(G<50)G+=60; 19 | G=parseInt(G).toString(16); 20 | 21 | var B=i*(i%10); 22 | for(;B>255;B-=255); 23 | if(B<50)B+=60; 24 | B=parseInt(B).toString(16); 25 | 26 | return "#"+R+G+B; 27 | } 28 | 29 | 30 | function getRandomColor() { 31 | const rgb = [] 32 | for (let i = 0 ; i < 3; ++i){ 33 | let color = Math.floor(Math.random() * 256).toString(16) 34 | color = color.length === 1 ? '0' + color : color 35 | rgb.push(color) 36 | } 37 | return '#' + rgb.join('') 38 | } 39 | 40 | export {getColorByIndex, getRandomColor} -------------------------------------------------------------------------------- /traclus-app/src/utils/event.js: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from "events" 2 | export default new EventEmitter() --------------------------------------------------------------------------------