From 2fa19599467263dcf582bb12906e03328e03b4a4 Mon Sep 17 00:00:00 2001 From: zhang <zc857179121@qq.com> Date: 星期三, 02 七月 2025 13:12:26 +0800 Subject: [PATCH] 初版提交 --- algorithm_system/__init__.py | 2 algorithm_system/models/agv_model.py | 572 + common/utils.py | 286 algorithm_system/algorithm_server.py | 623 + config/environment.py | 360 algorithm_system/models/__init__.py | 2 common/data_models.py | 184 run_algorithm.py | 216 common/__init__.py | 2 algorithm_system/algorithms/collision_detection.py | 701 + .idea/.gitignore | 10 .idea/misc.xml | 4 common/api_client.py | 124 algorithm_system/algorithms/__init__.py | 2 RCS与算法的接口协议v1.1.docx | 0 environment.json | 14960 +++++++++++++++++++++++++++ algorithm_system/algorithms/task_allocation.py | 687 + algorithm_system/path_monitor.py | 442 config/settings.py | 27 path_mapping.json | 11880 ++++++++++++++++++++++ ZY_algorithm_system.zip | 0 algorithm_system/algorithms/path_planning.py | 1131 ++ 22 files changed, 32,215 insertions(+), 0 deletions(-) diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..7bc07ec --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Environment-dependent path to Maven home directory +/mavenHomeManager.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..90dee70 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="KubernetesApiProvider">{}</component> +</project> \ No newline at end of file diff --git "a/RCS\344\270\216\347\256\227\346\263\225\347\232\204\346\216\245\345\217\243\345\215\217\350\256\256v1.1.docx" "b/RCS\344\270\216\347\256\227\346\263\225\347\232\204\346\216\245\345\217\243\345\215\217\350\256\256v1.1.docx" new file mode 100644 index 0000000..1b75e87 --- /dev/null +++ "b/RCS\344\270\216\347\256\227\346\263\225\347\232\204\346\216\245\345\217\243\345\215\217\350\256\256v1.1.docx" Binary files differ diff --git a/ZY_algorithm_system.zip b/ZY_algorithm_system.zip new file mode 100644 index 0000000..9c6c868 --- /dev/null +++ b/ZY_algorithm_system.zip Binary files differ diff --git a/algorithm_system/__init__.py b/algorithm_system/__init__.py new file mode 100644 index 0000000..444220e --- /dev/null +++ b/algorithm_system/__init__.py @@ -0,0 +1,2 @@ +# Algorithm System Module +__version__ = "1.0.0" \ No newline at end of file diff --git a/algorithm_system/algorithm_server.py b/algorithm_system/algorithm_server.py new file mode 100644 index 0000000..f338288 --- /dev/null +++ b/algorithm_system/algorithm_server.py @@ -0,0 +1,623 @@ +""" +绠楁硶绯荤粺鏈嶅姟鍣� +""" +import json +import logging +import os +import time +from typing import Dict, List, Optional, Any, Tuple +from flask import Flask, request, jsonify + +from common.data_models import ( + TaskData, AGVStatus, TaskAssignment, PlannedPath, + create_success_response, create_error_response, ResponseCode, to_dict, from_dict +) +from common.utils import load_path_mapping +from algorithm_system.models.agv_model import AGVModelManager +from algorithm_system.algorithms.task_allocation import TaskAllocationFactory +from algorithm_system.algorithms.path_planning import PathPlanningFactory +from common.api_client import RCSAPIClient +from algorithm_system.path_monitor import PathMonitorService + +try: + from config.settings import ( + ALGORITHM_SERVER_HOST, ALGORITHM_SERVER_PORT, + RCS_SERVER_HOST, RCS_SERVER_PORT, + MONITOR_POLLING_INTERVAL + ) +except ImportError: + ALGORITHM_SERVER_HOST = "10.10.10.239" + ALGORITHM_SERVER_PORT = 8002 + RCS_SERVER_HOST = "10.10.10.156" + RCS_SERVER_PORT = 8088 + MONITOR_POLLING_INTERVAL = 5.0 + logging.warning("鏃犳硶浠巆onfig.settings瀵煎叆閰嶇疆锛屼娇鐢ㄩ粯璁ゅ��") + + +class AlgorithmServer: + """绠楁硶绯荤粺鏈嶅姟鍣�""" + + def __init__(self, host: str = None, port: int = None, enable_path_monitor: bool = True, + monitor_interval: float = None): + """鍒濆鍖栫畻娉曟湇鍔″櫒""" + self.host = host or ALGORITHM_SERVER_HOST + self.port = port or ALGORITHM_SERVER_PORT + self.enable_path_monitor = enable_path_monitor + self.monitor_interval = monitor_interval or MONITOR_POLLING_INTERVAL + self.logger = logging.getLogger(__name__) + + self.app = Flask(__name__) + + self.path_mapping = load_path_mapping() + + self.agv_manager = AGVModelManager(self.path_mapping) + + self.rcs_client = RCSAPIClient() + + self.task_allocation_algorithm = "LOAD_BALANCED" # 榛樿浠诲姟鍒嗛厤 + self.path_planning_algorithm = "A_STAR" # 榛樿璺緞瑙勫垝 + + self.path_monitor = None + if self.enable_path_monitor: + self.path_monitor = PathMonitorService( + rcs_host=RCS_SERVER_HOST, + rcs_port=RCS_SERVER_PORT, + poll_interval=self.monitor_interval, + path_algorithm=self.path_planning_algorithm, + auto_send_paths=True # 鑷姩鍙戦�佽矾寰勫埌RCS + ) + self.logger.info(f"璺緞鐩戞帶鏈嶅姟宸插垵濮嬪寲 - 杞闂撮殧: {self.monitor_interval}s, 鑷姩鍙戦�佽矾寰�: True") + + self._setup_routes() + + self.logger.info("绠楁硶绯荤粺鏈嶅姟鍣ㄥ垵濮嬪寲瀹屾垚") + + def _setup_routes(self): + """璁剧疆API璺敱""" + + @self.app.route('/open/task/send/v1', methods=['POST']) + def task_send_endpoint(): + """浠诲姟涓嬪彂鎺ュ彛""" + return self._handle_task_send_request() + + @self.app.route('/open/path/plan/v1', methods=['POST']) + def path_plan_endpoint(): + """璺緞瑙勫垝鎺ュ彛""" + return self._handle_path_planning_request() + + @self.app.route('/open/path/batch/plan/v1', methods=['POST']) + def batch_path_plan_endpoint(): + """鎵归噺璺緞瑙勫垝鎺ュ彛""" + return self._handle_batch_path_planning_request() + + @self.app.route('/open/algorithm/config/v1', methods=['POST']) + def algorithm_config_endpoint(): + """绠楁硶閰嶇疆鎺ュ彛""" + return self._handle_algorithm_config_request() + + @self.app.route('/monitor/path/start/v1', methods=['POST']) + def start_path_monitor_endpoint(): + """鍚姩璺緞鐩戞帶鏈嶅姟鎺ュ彛""" + return self._handle_start_path_monitor_request() + + @self.app.route('/monitor/path/stop/v1', methods=['POST']) + def stop_path_monitor_endpoint(): + """鍋滄璺緞鐩戞帶鏈嶅姟鎺ュ彛""" + return self._handle_stop_path_monitor_request() + + @self.app.route('/monitor/path/status/v1', methods=['GET']) + def path_monitor_status_endpoint(): + """璺緞鐩戞帶鏈嶅姟鐘舵�佹煡璇㈡帴鍙�""" + return self._handle_path_monitor_status_request() + + @self.app.route('/health', methods=['GET']) + def health_check(): + """鍋ュ悍妫�鏌ユ帴鍙�""" + monitor_status = None + if self.path_monitor: + monitor_status = self.path_monitor.get_monitoring_status() + + return jsonify({ + "status": "healthy", + "service": "algorithm_system", + "timestamp": time.time(), + "path_mapping_loaded": len(self.path_mapping) > 0, + "algorithms": { + "task_allocation": self.task_allocation_algorithm, + "path_planning": self.path_planning_algorithm + }, + "agv_count": len(self.agv_manager.get_all_agvs()), + "path_monitor": monitor_status, + "available_endpoints": [ + "POST /open/task/send/v1 - 浠诲姟鍒嗛厤鎺ュ彛", + "POST /open/path/plan/v1 - 鍗曡矾寰勮鍒掓帴鍙�", + "POST /open/path/batch/plan/v1 - 鎵归噺璺緞瑙勫垝鎺ュ彛", + "POST /open/algorithm/config/v1 - 绠楁硶閰嶇疆鎺ュ彛", + "POST /monitor/path/start/v1 - 鍚姩璺緞鐩戞帶鏈嶅姟", + "POST /monitor/path/stop/v1 - 鍋滄璺緞鐩戞帶鏈嶅姟", + "GET /monitor/path/status/v1 - 璺緞鐩戞帶鏈嶅姟鐘舵�佹煡璇�", + "GET /health - 鍋ュ悍妫�鏌ユ帴鍙�" + ] + }) + + @self.app.errorhandler(404) + def not_found(error): + """404閿欒澶勭悊""" + return jsonify(to_dict(create_error_response(404, "鎺ュ彛涓嶅瓨鍦�"))), 404 + + @self.app.errorhandler(500) + def internal_error(error): + """500閿欒澶勭悊""" + self.logger.error(f"鍐呴儴鏈嶅姟鍣ㄩ敊璇�: {error}") + return jsonify(to_dict(create_error_response(ResponseCode.SERVER_ERROR, "鍐呴儴鏈嶅姟鍣ㄩ敊璇�"))), 500 + + def _handle_task_send_request(self) -> Dict[str, Any]: + """澶勭悊浠诲姟涓嬪彂璇锋眰""" + try: + if not request.is_json: + return jsonify(to_dict(create_error_response(ResponseCode.PARAM_EMPTY, "璇锋眰鏁版嵁鏍煎紡閿欒"))) + + request_data = request.get_json() + if not request_data: + return jsonify(to_dict(create_error_response(ResponseCode.PARAM_EMPTY, "璇锋眰鏁版嵁涓虹┖"))) + + if isinstance(request_data, dict) and "tasks" in request_data: + task_data = request_data.get("tasks", []) + agv_status_data = request_data.get("agvStatus", []) + self.logger.info(f"鏀跺埌缁撴瀯鍖栦换鍔″垎閰嶈姹傦紝浠诲姟鏁伴噺: {len(task_data)}, AGV鏁伴噺: {len(agv_status_data)}") + elif isinstance(request_data, list): + task_data = request_data + agv_status_data = [] + self.logger.info(f"鏀跺埌浠诲姟鍒嗛厤璇锋眰锛屼换鍔℃暟閲�: {len(task_data)}") + else: + return jsonify(to_dict(create_error_response(ResponseCode.PARAM_EMPTY, "浠诲姟鏁版嵁鏍煎紡鏃犳晥"))) + + if not task_data or not isinstance(task_data, list): + return jsonify(to_dict(create_error_response(ResponseCode.PARAM_EMPTY, "浠诲姟鏁版嵁鏃犳晥"))) + + tasks = [] + for task_dict in task_data: + try: + task = TaskData( + taskId=task_dict.get("taskId", ""), + start=task_dict.get("start", ""), + end=task_dict.get("end", ""), + type=task_dict.get("type", "1"), + priority=task_dict.get("priority", 5) + ) + tasks.append(task) + except Exception as e: + self.logger.warning(f"瑙f瀽浠诲姟鏁版嵁澶辫触: {task_dict} - {e}") + continue + + if not tasks: + return jsonify(to_dict(create_error_response(ResponseCode.PARAM_EMPTY, "娌℃湁鏈夋晥鐨勪换鍔℃暟鎹�"))) + + agv_status_list = [] + if agv_status_data: + self.logger.info("浣跨敤璇锋眰涓彁渚涚殑AGV鐘舵�佹暟鎹�") + for agv_data in agv_status_data: + try: + agv_status = from_dict(AGVStatus, agv_data) + agv_status_list.append(agv_status) + except Exception as e: + self.logger.warning(f"瑙f瀽璇锋眰涓殑AGV鐘舵�佹暟鎹け璐�: {agv_data} - {e}") + continue + else: + self.logger.info("浠嶳CS绯荤粺鑾峰彇AGV鐘舵�佹暟鎹�") + agv_response = self.rcs_client.get_agv_status() + if agv_response.code == ResponseCode.SUCCESS and agv_response.data: + for agv_data in agv_response.data: + try: + agv_status = from_dict(AGVStatus, agv_data) + agv_status_list.append(agv_status) + except Exception as e: + self.logger.warning(f"瑙f瀽RCS杩斿洖鐨凙GV鐘舵�佹暟鎹け璐�: {agv_data} - {e}") + continue + else: + error_msg = f"鏃犳硶鑾峰彇AGV鐘舵��: {agv_response.msg if agv_response else 'RCS绯荤粺杩炴帴澶辫触'}" + self.logger.error(error_msg) + + use_fallback_allocation = getattr(self, 'use_fallback_allocation', True) + + if use_fallback_allocation: + self.logger.warning("浣跨敤杞鍒嗛厤浣滀负澶囩敤鏂规") + return self._simple_round_robin_allocation(tasks) + else: + return jsonify(to_dict(create_error_response( + ResponseCode.SERVER_ERROR, + error_msg + ))) + + if not agv_status_list: + return jsonify(to_dict(create_error_response(ResponseCode.NO_DATA, "娌℃湁鍙敤鐨凙GV鐘舵�佹暟鎹�"))) + + self.agv_manager.update_agv_data(agv_status_list) + self.logger.info(f"鏇存柊浜� {len(agv_status_list)} 涓狝GV鐘舵��") + + all_agvs = self.agv_manager.get_all_agvs() + self.logger.info(f"绠楁硶绯荤粺涓綋鍓嶆湁 {len(all_agvs)} 涓狝GV妯″瀷") + + available_count = 0 + for agv in all_agvs: + can_accept = agv.can_accept_task(5) + self.logger.debug(f"AGV {agv.agvId}: status={agv.status}, can_accept_task={can_accept}, " + f"is_overloaded={agv.is_overloaded()}, task_count={agv.current_task_count}") + if can_accept: + available_count += 1 + + self.logger.info(f"鍏朵腑 {available_count} 涓狝GV鍙互鎺ュ彈浠诲姟") + + if available_count == 0: + self.logger.warning("娌℃湁AGV鍙互鎺ュ彈浠诲姟锛佽缁嗙姸鎬�:") + for agv in all_agvs: + status_str = str(agv.status) + self.logger.warning(f" AGV {agv.agvId}: 鐘舵��={status_str}, 绫诲瀷={type(agv.status)}, " + f"浠诲姟鏁�={agv.current_task_count}/{agv.max_capacity}") + + allocator = TaskAllocationFactory.create_allocator( + self.task_allocation_algorithm, + self.agv_manager + ) + + start_time = time.time() + assignments = allocator.allocate_tasks(tasks) + end_time = time.time() + + allocation_time = (end_time - start_time) * 1000 # 杞崲涓烘绉� + + assignment_data = [to_dict(assignment) for assignment in assignments] + + self.logger.info(f"浠诲姟鍒嗛厤瀹屾垚锛屽垎閰嶄簡 {len(assignments)} 涓换鍔★紝鑰楁椂: {allocation_time:.2f}ms") + + response = create_success_response(assignment_data) + return jsonify(to_dict(response)) + + except Exception as e: + self.logger.error(f"澶勭悊浠诲姟鍒嗛厤璇锋眰澶辫触: {e}", exc_info=True) + return jsonify(to_dict(create_error_response(ResponseCode.SERVER_ERROR, f"鏈嶅姟鍣ㄩ敊璇�: {str(e)}"))) + + def _handle_path_planning_request(self) -> Dict[str, Any]: + """澶勭悊璺緞瑙勫垝璇锋眰""" + try: + # 楠岃瘉璇锋眰鏁版嵁 + if not request.is_json: + return jsonify(to_dict(create_error_response(ResponseCode.PARAM_EMPTY, "璇锋眰鏁版嵁鏍煎紡閿欒"))) + + request_data = request.get_json() + if not request_data: + return jsonify(to_dict(create_error_response(ResponseCode.PARAM_EMPTY, "璇锋眰鏁版嵁涓虹┖"))) + + agv_id = request_data.get("agvId") + start_code = request_data.get("start") + end_code = request_data.get("end") + constraints = request_data.get("constraints", []) + + if not agv_id or not start_code or not end_code: + return jsonify(to_dict(create_error_response(ResponseCode.PARAM_EMPTY, "缂哄皯蹇呰鍙傛暟"))) + + self.logger.info(f"鏀跺埌璺緞瑙勫垝璇锋眰 - AGV: {agv_id}, 浠� {start_code} 鍒� {end_code}") + + # 鍒涘缓璺緞瑙勫垝鍣� + path_planner = PathPlanningFactory.create_path_planner( + self.path_planning_algorithm, + self.path_mapping + ) + + # 鎵ц璺緞瑙勫垝 + start_time = time.time() + planned_path = path_planner.plan_path(start_code, end_code, constraints) + end_time = time.time() + + planning_time = (end_time - start_time) * 1000 # 杞崲涓烘绉� + + if planned_path: + # 璁剧疆AGV ID + planned_path.agvId = agv_id + + # 杞崲涓哄搷搴旀牸寮� + path_data = to_dict(planned_path) + + self.logger.info(f"璺緞瑙勫垝瀹屾垚 - AGV: {agv_id}, 璺緞闀垮害: {len(planned_path.codeList)}, 鑰楁椂: {planning_time:.2f}ms") + + response = create_success_response(path_data) + return jsonify(to_dict(response)) + else: + self.logger.warning(f"璺緞瑙勫垝澶辫触 - AGV: {agv_id}, 浠� {start_code} 鍒� {end_code}") + return jsonify(to_dict(create_error_response(ResponseCode.SERVER_ERROR, "鏃犳硶瑙勫垝璺緞"))) + + except Exception as e: + self.logger.error(f"澶勭悊璺緞瑙勫垝璇锋眰澶辫触: {e}", exc_info=True) + return jsonify(to_dict(create_error_response(ResponseCode.SERVER_ERROR, f"鏈嶅姟鍣ㄩ敊璇�: {str(e)}"))) + + def _handle_batch_path_planning_request(self) -> Dict[str, Any]: + """澶勭悊鎵归噺璺緞瑙勫垝璇锋眰""" + try: + if not request.is_json: + request_data = {} + else: + request_data = request.get_json() or {} + + constraints = request_data.get("constraints", []) + include_idle_agv = request_data.get("includeIdleAgv", False) + use_enhanced_planning = request_data.get("useEnhancedPlanning", True) + + agv_status_from_request = ( + request_data.get("agvStatusList", []) or + request_data.get("agvs", []) + ) + + self.logger.info(f"鏀跺埌鎵归噺璺緞瑙勫垝璇锋眰锛屽寘鍚┖闂睞GV: {include_idle_agv}, " + f"璇锋眰涓瑼GV鏁伴噺: {len(agv_status_from_request)}, " + f"浣跨敤澧炲己瑙勫垝: {use_enhanced_planning}") + + agv_status_list = [] + if agv_status_from_request: + self.logger.info("浣跨敤璇锋眰涓彁渚涚殑AGV鐘舵�佹暟鎹繘琛岃矾寰勮鍒�") + for agv_data in agv_status_from_request: + try: + agv_status = from_dict(AGVStatus, agv_data) + agv_status_list.append(agv_status) + except Exception as e: + self.logger.warning(f"瑙f瀽璇锋眰涓殑AGV鐘舵�佹暟鎹け璐�: {agv_data} - {e}") + continue + else: + self.logger.info("浠嶳CS绯荤粺鑾峰彇AGV鐘舵�佹暟鎹繘琛岃矾寰勮鍒�") + agv_response = self.rcs_client.get_agv_status() + if agv_response.code != ResponseCode.SUCCESS or not agv_response.data: + error_msg = f"鏃犳硶鑾峰彇AGV鐘舵�佽繘琛岃矾寰勮鍒�: {agv_response.msg if agv_response else 'RCS绯荤粺杩炴帴澶辫触'}" + self.logger.error(error_msg) + return jsonify(to_dict(create_error_response(ResponseCode.SERVER_ERROR, error_msg))) + + for agv_data in agv_response.data: + try: + agv_status = from_dict(AGVStatus, agv_data) + agv_status_list.append(agv_status) + except Exception as e: + self.logger.warning(f"瑙f瀽RCS杩斿洖鐨凙GV鐘舵�佹暟鎹け璐�: {agv_data} - {e}") + continue + + if not agv_status_list: + error_msg = "娌℃湁鍙敤鐨凙GV鐘舵�佹暟鎹繘琛岃矾寰勮鍒�" + self.logger.error(error_msg) + return jsonify(to_dict(create_error_response(ResponseCode.NO_DATA, error_msg))) + + # 鏇存柊AGV妯″瀷绠$悊鍣� + self.agv_manager.update_agv_data(agv_status_list) + + self.logger.info(f"寮哄埗浣跨敤璺緞瑙勫垝浠ョ‘淇濆寘鍚柊瀛楁") + result = self._enhanced_batch_path_planning(agv_status_list, include_idle_agv, constraints) + + response = create_success_response(result) + return jsonify(to_dict(response)) + + except Exception as e: + self.logger.error(f"澶勭悊鎵归噺璺緞瑙勫垝璇锋眰澶辫触: {e}", exc_info=True) + return jsonify(to_dict(create_error_response(ResponseCode.SERVER_ERROR, f"鏈嶅姟鍣ㄩ敊璇�: {str(e)}"))) + + def _enhanced_batch_path_planning(self, agv_status_list: List[AGVStatus], + include_idle_agv: bool, + constraints: List[Tuple[int, int, float]]) -> Dict[str, Any]: + """鎵归噺璺緞瑙勫垝""" + from algorithm_system.algorithms.path_planning import PathPlanningFactory + + # 鍒涘缓鎵归噺璺緞瑙勫垝鍣� + batch_planner = PathPlanningFactory.create_batch_path_planner( + algorithm_type=self.path_planning_algorithm, + path_mapping=self.path_mapping + ) + + # 鎵ц鎵归噺璺緞瑙勫垝 + result = batch_planner.plan_all_agv_paths( + agv_status_list=agv_status_list, + include_idle_agv=include_idle_agv, + constraints=constraints + ) + + # 鎻愬彇plannedPaths浣滀负鏈�缁堣繑鍥炴暟鎹� + planned_paths = result.get('plannedPaths', []) + + self.logger.info(f"鎵归噺璺緞瑙勫垝瀹屾垚 - 鎬籄GV: {result['totalAgvs']}, " + f"鎵ц浠诲姟: {result['executingTasksCount']}, " + f"瑙勫垝璺緞: {result['plannedPathsCount']}, " + f"鍐茬獊妫�娴�: {result['conflictsDetected']}, " + f"鎬昏�楁椂: {result['totalPlanningTime']:.2f}ms") + + # 鍙繑鍥炶矾寰勬暟缁勶紝涓嶅寘鍚叾浠栫粺璁′俊鎭� + return planned_paths + + def _handle_algorithm_config_request(self) -> Dict[str, Any]: + """ + 澶勭悊绠楁硶閰嶇疆璇锋眰 + + Returns: + Dict: 鍝嶅簲鏁版嵁 + """ + try: + # 楠岃瘉璇锋眰鏁版嵁 + if not request.is_json: + return jsonify(to_dict(create_error_response(ResponseCode.PARAM_EMPTY, "璇锋眰鏁版嵁鏍煎紡閿欒"))) + + config_data = request.get_json() + if not config_data: + return jsonify(to_dict(create_error_response(ResponseCode.PARAM_EMPTY, "閰嶇疆鏁版嵁涓虹┖"))) + + # 鏇存柊绠楁硶閰嶇疆 + if "task_allocation_algorithm" in config_data: + self.task_allocation_algorithm = config_data["task_allocation_algorithm"] + self.logger.info(f"浠诲姟鍒嗛厤绠楁硶鏇存柊涓�: {self.task_allocation_algorithm}") + + if "path_planning_algorithm" in config_data: + self.path_planning_algorithm = config_data["path_planning_algorithm"] + self.logger.info(f"璺緞瑙勫垝绠楁硶鏇存柊涓�: {self.path_planning_algorithm}") + + # 杩斿洖褰撳墠閰嶇疆 + current_config = { + "task_allocation_algorithm": self.task_allocation_algorithm, + "path_planning_algorithm": self.path_planning_algorithm + } + + response = create_success_response(current_config) + return jsonify(to_dict(response)) + + except Exception as e: + self.logger.error(f"澶勭悊绠楁硶閰嶇疆璇锋眰澶辫触: {e}", exc_info=True) + return jsonify(to_dict(create_error_response(ResponseCode.SERVER_ERROR, f"鏈嶅姟鍣ㄩ敊璇�: {str(e)}"))) + + def _simple_round_robin_allocation(self, tasks: List[TaskData]) -> Dict[str, Any]: + """绠�鍗曠殑杞鍒嗛厤""" + assignments = [] + + # 灏濊瘯浠嶳CS绯荤粺鑾峰彇AGV鏁伴噺 + agv_count = 5 # 榛樿鍊� + try: + agv_response = self.rcs_client.get_agv_status() + if agv_response.code == ResponseCode.SUCCESS and agv_response.data: + agv_count = len(agv_response.data) + self.logger.info(f"浠嶳CS绯荤粺鑾峰彇鍒癆GV鏁伴噺: {agv_count}") + else: + # 濡傛灉RCS杩斿洖鏃犳暟鎹紝浣跨敤閰嶇疆鐨勯粯璁ゅ�� + from config.settings import DEFAULT_AGV_COUNT + agv_count = DEFAULT_AGV_COUNT + self.logger.warning(f"鏃犳硶浠嶳CS鑾峰彇AGV鏁伴噺锛屼娇鐢ㄩ粯璁ゅ��: {agv_count}") + except Exception as e: + # 濡傛灉瀹屽叏鏃犳硶杩炴帴RCS锛屼娇鐢ㄩ厤缃殑榛樿鍊� + from config.settings import DEFAULT_AGV_COUNT + agv_count = DEFAULT_AGV_COUNT + self.logger.error(f"鑾峰彇AGV鏁伴噺澶辫触锛屼娇鐢ㄩ粯璁ゅ��: {agv_count} - {e}") + + for i, task in enumerate(tasks): + agv_id = f"AGV_{10001 + (i % agv_count)}" + assignment = TaskAssignment( + taskId=task.taskId, + agvId=agv_id + ) + assignments.append(assignment) + + assignment_data = [to_dict(assignment) for assignment in assignments] + + self.logger.info(f"浣跨敤绠�鍗曡疆璇㈠垎閰嶄簡 {len(assignments)} 涓换鍔★紝AGV鏁伴噺: {agv_count}") + + response = create_success_response(assignment_data) + return jsonify(to_dict(response)) + + def _handle_start_path_monitor_request(self) -> Dict[str, Any]: + """澶勭悊鍚姩璺緞鐩戞帶鏈嶅姟璇锋眰""" + try: + if not self.path_monitor: + return jsonify(to_dict(create_error_response(ResponseCode.SERVER_ERROR, "璺緞鐩戞帶鏈嶅姟鏈垵濮嬪寲"))) + + if self.path_monitor.is_running: + return jsonify(to_dict(create_success_response({"message": "璺緞鐩戞帶鏈嶅姟宸插湪杩愯涓�"}))) + + # 鍚姩璺緞鐩戞帶鏈嶅姟 + self.path_monitor.start_monitoring() + + self.logger.info("璺緞鐩戞帶鏈嶅姟宸查�氳繃API鍚姩") + + response = create_success_response({ + "message": "璺緞鐩戞帶鏈嶅姟鍚姩鎴愬姛", + "monitor_status": self.path_monitor.get_monitoring_status() + }) + + return jsonify(to_dict(response)) + + except Exception as e: + self.logger.error(f"鍚姩璺緞鐩戞帶鏈嶅姟澶辫触: {e}") + return jsonify(to_dict(create_error_response(ResponseCode.SERVER_ERROR, f"鍚姩璺緞鐩戞帶鏈嶅姟澶辫触: {str(e)}"))) + + def _handle_stop_path_monitor_request(self) -> Dict[str, Any]: + """澶勭悊鍋滄璺緞鐩戞帶鏈嶅姟璇锋眰""" + try: + if not self.path_monitor: + return jsonify(to_dict(create_error_response(ResponseCode.SERVER_ERROR, "璺緞鐩戞帶鏈嶅姟鏈垵濮嬪寲"))) + + if not self.path_monitor.is_running: + return jsonify(to_dict(create_success_response({"message": "璺緞鐩戞帶鏈嶅姟鏈湪杩愯"}))) + + # 鍋滄璺緞鐩戞帶鏈嶅姟 + self.path_monitor.stop_monitoring() + + self.logger.info("璺緞鐩戞帶鏈嶅姟宸查�氳繃API鍋滄") + + response = create_success_response({ + "message": "璺緞鐩戞帶鏈嶅姟鍋滄鎴愬姛", + "monitor_status": self.path_monitor.get_monitoring_status() + }) + + return jsonify(to_dict(response)) + + except Exception as e: + self.logger.error(f"鍋滄璺緞鐩戞帶鏈嶅姟澶辫触: {e}") + return jsonify(to_dict(create_error_response(ResponseCode.SERVER_ERROR, f"鍋滄璺緞鐩戞帶鏈嶅姟澶辫触: {str(e)}"))) + + def _handle_path_monitor_status_request(self) -> Dict[str, Any]: + """澶勭悊璺緞鐩戞帶鏈嶅姟鐘舵�佹煡璇㈣姹�""" + try: + if not self.path_monitor: + return jsonify(to_dict(create_error_response(ResponseCode.SERVER_ERROR, "璺緞鐩戞帶鏈嶅姟鏈垵濮嬪寲"))) + + monitor_status = self.path_monitor.get_monitoring_status() + + response = create_success_response({ + "monitor_status": monitor_status, + "description": "璺緞鐩戞帶鏈嶅姟鐘舵�佷俊鎭�" + }) + + return jsonify(to_dict(response)) + + except Exception as e: + self.logger.error(f"鑾峰彇璺緞鐩戞帶鏈嶅姟鐘舵�佸け璐�: {e}") + return jsonify(to_dict(create_error_response(ResponseCode.SERVER_ERROR, f"鑾峰彇璺緞鐩戞帶鏈嶅姟鐘舵�佸け璐�: {str(e)}"))) + + def start_server(self): + """鍚姩绠楁硶鏈嶅姟鍣�""" + self.logger.info(f"鍚姩绠楁硶绯荤粺鏈嶅姟鍣�: http://{self.host}:{self.port}") + + try: + # 濡傛灉鍚敤浜嗚矾寰勭洃鎺ф湇鍔★紝鑷姩鍚姩 + if self.path_monitor and self.enable_path_monitor: + self.path_monitor.start_monitoring() + self.logger.info("璺緞鐩戞帶鏈嶅姟宸茶嚜鍔ㄥ惎鍔�") + + self.app.run( + host=self.host, + port=self.port, + debug=False, + threaded=True + ) + except Exception as e: + self.logger.error(f"鍚姩绠楁硶鏈嶅姟鍣ㄥけ璐�: {e}") + # 纭繚鍦ㄥ惎鍔ㄥけ璐ユ椂鍋滄璺緞鐩戞帶鏈嶅姟 + if self.path_monitor and self.path_monitor.is_running: + self.path_monitor.stop_monitoring() + raise + + def stop_server(self): + """鍋滄绠楁硶鏈嶅姟鍣�""" + self.logger.info("鍋滄绠楁硶鏈嶅姟鍣�") + + # 鍋滄璺緞鐩戞帶鏈嶅姟 + if self.path_monitor and self.path_monitor.is_running: + self.path_monitor.stop_monitoring() + self.logger.info("璺緞鐩戞帶鏈嶅姟宸插仠姝�") + + + def get_server_status(self) -> Dict[str, Any]: + """鑾峰彇鏈嶅姟鍣ㄧ姸鎬�""" + monitor_status = None + if self.path_monitor: + monitor_status = self.path_monitor.get_monitoring_status() + + return { + "host": self.host, + "port": self.port, + "path_mapping_loaded": len(self.path_mapping) > 0, + "agv_count": len(self.agv_manager.get_all_agvs()), + "algorithms": { + "task_allocation": self.task_allocation_algorithm, + "path_planning": self.path_planning_algorithm + }, + "path_monitor": monitor_status, + "timestamp": time.time() + } \ No newline at end of file diff --git a/algorithm_system/algorithms/__init__.py b/algorithm_system/algorithms/__init__.py new file mode 100644 index 0000000..57ac5fa --- /dev/null +++ b/algorithm_system/algorithms/__init__.py @@ -0,0 +1,2 @@ +# Algorithm System Algorithms Module +__version__ = "1.0.0" \ No newline at end of file diff --git a/algorithm_system/algorithms/collision_detection.py b/algorithm_system/algorithms/collision_detection.py new file mode 100644 index 0000000..5b099bf --- /dev/null +++ b/algorithm_system/algorithms/collision_detection.py @@ -0,0 +1,701 @@ +""" +AGV璺緞纰版挒妫�娴嬪拰瑙e喅妯″潡 +""" +import math +import logging +from typing import Dict, List, Tuple, Optional, Set +from dataclasses import dataclass +from collections import defaultdict + +from common.data_models import PlannedPath, PathCode, AGVActionTypeEnum +from common.utils import get_coordinate_from_path_id + + +@dataclass +class SpaceTimeNode: + """鏃剁┖鑺傜偣 - 琛ㄧずAGV鍦ㄧ壒瀹氭椂闂村拰浣嶇疆鐨勭姸鎬�""" + agv_id: str + position: str # 浣嶇疆鐮� + coordinates: Tuple[int, int] # 鍧愭爣 + time_step: int # 鏃堕棿姝� + direction: str # 鏂瑰悜 + + +@dataclass +class Conflict: + """鍐茬獊鎻忚堪""" + type: str # 鍐茬獊绫诲瀷: "vertex", "edge", "follow" + agv1: str + agv2: str + time_step: int + position1: str + position2: str + description: str + + +@dataclass +class AGVPriority: + """AGV浼樺厛绾ц瘎浼扮粨鏋�""" + agv_id: str + priority_score: float # 浼樺厛绾у垎鏁帮紝瓒婇珮瓒婁紭鍏� + task_status: str # 浠诲姟鐘舵�� + action_type: str # 鍔ㄤ綔绫诲瀷 + task_priority: int # 浠诲姟浼樺厛绾� + voltage: int # 鐢甸噺 + explanation: str # 浼樺厛绾ц鏄� + + +class CollisionDetector: + """AGV璺緞纰版挒妫�娴嬪櫒""" + + def __init__(self, path_mapping: Dict[str, Dict[str, int]], + min_distance: float = 3.0, time_buffer: int = 1): + """ + 鍒濆鍖栫鎾炴娴嬪櫒 + + Args: + path_mapping: 璺緞鐐规槧灏勫瓧鍏� + min_distance: AGV涔嬮棿鐨勬渶灏忓畨鍏ㄨ窛绂� + time_buffer: 鏃堕棿缂撳啿鍖猴紙鏃堕棿姝ユ暟锛� + """ + self.path_mapping = path_mapping + self.min_distance = min_distance + self.time_buffer = time_buffer + self.logger = logging.getLogger(__name__) + + def detect_conflicts(self, planned_paths: List[Dict]) -> List[Conflict]: + """ + 妫�娴嬫墍鏈堿GV璺緞涔嬮棿鐨勫啿绐� + + Args: + planned_paths: 瑙勫垝璺緞鍒楄〃 + + Returns: + List[Conflict]: 鍐茬獊鍒楄〃 + """ + conflicts = [] + + # 鏋勫缓鏃剁┖琛� + space_time_table = self._build_space_time_table(planned_paths) + + # 妫�娴嬮《鐐瑰啿绐侊紙鍚屼竴鏃堕棿鍚屼竴浣嶇疆锛� + conflicts.extend(self._detect_vertex_conflicts(space_time_table)) + + # 妫�娴嬭竟鍐茬獊锛圓GV浜ゆ崲浣嶇疆锛� + conflicts.extend(self._detect_edge_conflicts(space_time_table)) + + # 妫�娴嬭窡闅忓啿绐侊紙AGV璺濈杩囪繎锛� + conflicts.extend(self._detect_following_conflicts(space_time_table)) + + self.logger.info(f"妫�娴嬪埌 {len(conflicts)} 涓矾寰勫啿绐�") + return conflicts + + def _build_space_time_table(self, planned_paths: List[Dict]) -> Dict[int, List[SpaceTimeNode]]: + """ + 鏋勫缓鏃剁┖琛� + + Args: + planned_paths: 瑙勫垝璺緞鍒楄〃 + + Returns: + Dict[int, List[SpaceTimeNode]]: 鏃堕棿姝� -> 鏃剁┖鑺傜偣鍒楄〃 + """ + space_time_table = defaultdict(list) + + for path_data in planned_paths: + agv_id = path_data.get('agvId', '') + code_list = path_data.get('codeList', []) + + for time_step, path_code in enumerate(code_list): + position = path_code.get('code', '') if isinstance(path_code, dict) else path_code.code + direction = path_code.get('direction', '90') if isinstance(path_code, dict) else path_code.direction + + coordinates = get_coordinate_from_path_id(position, self.path_mapping) + if coordinates: + node = SpaceTimeNode( + agv_id=agv_id, + position=position, + coordinates=coordinates, + time_step=time_step, + direction=direction + ) + space_time_table[time_step].append(node) + + return space_time_table + + def _detect_vertex_conflicts(self, space_time_table: Dict[int, List[SpaceTimeNode]]) -> List[Conflict]: + """妫�娴嬮《鐐瑰啿绐侊紙鍚屼竴鏃堕棿鍚屼竴浣嶇疆锛�""" + conflicts = [] + + for time_step, nodes in space_time_table.items(): + # 鎸変綅缃垎缁� + position_groups = defaultdict(list) + for node in nodes: + position_groups[node.position].append(node) + + # 妫�鏌ユ瘡涓綅缃槸鍚︽湁澶氫釜AGV + for position, agv_nodes in position_groups.items(): + if len(agv_nodes) > 1: + # 鍙戠幇鍐茬獊 + for i in range(len(agv_nodes)): + for j in range(i + 1, len(agv_nodes)): + conflict = Conflict( + type="vertex", + agv1=agv_nodes[i].agv_id, + agv2=agv_nodes[j].agv_id, + time_step=time_step, + position1=position, + position2=position, + description=f"AGV {agv_nodes[i].agv_id} 鍜� {agv_nodes[j].agv_id} 鍦ㄦ椂闂� {time_step} 鍗犵敤鍚屼竴浣嶇疆 {position}" + ) + conflicts.append(conflict) + + return conflicts + + def _detect_edge_conflicts(self, space_time_table: Dict[int, List[SpaceTimeNode]]) -> List[Conflict]: + """妫�娴嬭竟鍐茬獊锛圓GV浜ゆ崲浣嶇疆锛�""" + conflicts = [] + + max_time = max(space_time_table.keys()) if space_time_table else 0 + + for time_step in range(max_time): + current_nodes = space_time_table.get(time_step, []) + next_nodes = space_time_table.get(time_step + 1, []) + + # 鏋勫缓褰撳墠鍜屼笅涓�鏃跺埢鐨勪綅缃槧灏� + current_positions = {node.agv_id: node.position for node in current_nodes} + next_positions = {node.agv_id: node.position for node in next_nodes} + + # 妫�鏌ユ槸鍚︽湁AGV浜ゆ崲浣嶇疆 + for agv1, pos1_current in current_positions.items(): + for agv2, pos2_current in current_positions.items(): + if agv1 >= agv2: # 閬垮厤閲嶅妫�鏌� + continue + + pos1_next = next_positions.get(agv1) + pos2_next = next_positions.get(agv2) + + if (pos1_next and pos2_next and + pos1_current == pos2_next and pos2_current == pos1_next): + # 鍙戠幇杈瑰啿绐� + conflict = Conflict( + type="edge", + agv1=agv1, + agv2=agv2, + time_step=time_step, + position1=pos1_current, + position2=pos2_current, + description=f"AGV {agv1} 鍜� {agv2} 鍦ㄦ椂闂� {time_step}-{time_step+1} 浜ゆ崲浣嶇疆 {pos1_current}<->{pos2_current}" + ) + conflicts.append(conflict) + + return conflicts + + def _detect_following_conflicts(self, space_time_table: Dict[int, List[SpaceTimeNode]]) -> List[Conflict]: + """妫�娴嬭窡闅忓啿绐侊紙AGV璺濈杩囪繎锛�""" + conflicts = [] + + for time_step, nodes in space_time_table.items(): + # 妫�鏌ユ墍鏈堿GV瀵逛箣闂寸殑璺濈 + for i in range(len(nodes)): + for j in range(i + 1, len(nodes)): + node1, node2 = nodes[i], nodes[j] + + # 璁$畻娆у嚑閲屽緱璺濈 + distance = math.sqrt( + (node1.coordinates[0] - node2.coordinates[0]) ** 2 + + (node1.coordinates[1] - node2.coordinates[1]) ** 2 + ) + + if distance < self.min_distance and distance > 0: + conflict = Conflict( + type="follow", + agv1=node1.agv_id, + agv2=node2.agv_id, + time_step=time_step, + position1=node1.position, + position2=node2.position, + description=f"AGV {node1.agv_id} 鍜� {node2.agv_id} 鍦ㄦ椂闂� {time_step} 璺濈杩囪繎 ({distance:.2f} < {self.min_distance})" + ) + conflicts.append(conflict) + + return conflicts + + +class CollisionResolver: + """AGV璺緞纰版挒瑙e喅鍣�""" + + def __init__(self, path_mapping: Dict[str, Dict[str, int]], detector: CollisionDetector, agv_manager=None): + """ + 鍒濆鍖栫鎾炶В鍐冲櫒 + + Args: + path_mapping: 璺緞鐐规槧灏勫瓧鍏� + detector: 纰版挒妫�娴嬪櫒 + agv_manager: AGV绠$悊鍣紝鐢ㄤ簬鑾峰彇AGV鐘舵�佷俊鎭� + """ + self.path_mapping = path_mapping + self.detector = detector + self.agv_manager = agv_manager + self.logger = logging.getLogger(__name__) + + def evaluate_agv_priority(self, agv_id: str, path: Dict, executing_tasks: List[Dict] = None) -> AGVPriority: + """ + 璇勪及AGV鐨勯伩璁╀紭鍏堢骇 + + Args: + agv_id: AGV ID + path: AGV璺緞淇℃伅 + executing_tasks: 鎵ц涓殑浠诲姟鍒楄〃 + + Returns: + AGVPriority: AGV浼樺厛绾ц瘎浼扮粨鏋� + """ + # 榛樿鍊� + task_status = "idle" + action_type = "unknown" + task_priority = 1 + voltage = 100 + priority_score = 0.0 + explanation_parts = [] + + # 鑾峰彇AGV鐘舵�佷俊鎭� + agv_model = None + if self.agv_manager: + agv_model = self.agv_manager.get_agv_by_id(agv_id) + if agv_model: + voltage = agv_model.voltage + + # 浠庤矾寰勪俊鎭腑鑾峰彇鍔ㄤ綔绫诲瀷 + code_list = path.get('codeList', []) + if code_list: + first_code = code_list[0] + if isinstance(first_code, dict): + action_type = first_code.get('type', 'unknown') + + # 浠庢墽琛屼腑浠诲姟鑾峰彇浠诲姟鐘舵�佸拰浼樺厛绾� + if executing_tasks: + for task in executing_tasks: + if task.get('agvId') == agv_id: + task_status = task.get('status', 'idle') + # 灏濊瘯浠庝换鍔D鑾峰彇浼樺厛绾э紙绠�鍖栧鐞嗭級 + task_id = task.get('taskId', '') + if 'HIGH_PRIORITY' in task_id: + task_priority = 10 + elif 'PRIORITY' in task_id: + task_priority = 8 + else: + task_priority = 5 + break + + # 璁$畻浼樺厛绾у垎鏁帮紙鍒嗘暟瓒婇珮浼樺厛绾ц秺楂橈紝瓒婁笉瀹规槗璁╂锛� + priority_score = 0.0 + + # 1. 浠诲姟鐘舵�佷紭鍏堢骇 (40%鏉冮噸) + if task_status == "executing": + priority_score += 40.0 + explanation_parts.append("鎵ц浠诲姟涓�(+40)") + elif task_status == "assigned": + priority_score += 20.0 + explanation_parts.append("宸插垎閰嶄换鍔�(+20)") + else: + priority_score += 5.0 + explanation_parts.append("绌洪棽鐘舵��(+5)") + + # 2. 鍔ㄤ綔绫诲瀷浼樺厛绾� (30%鏉冮噸) + if action_type == AGVActionTypeEnum.TASK.value: + priority_score += 30.0 + explanation_parts.append("浠诲姟琛屼负(+30)") + elif action_type == AGVActionTypeEnum.CHARGING.value: + priority_score += 25.0 + explanation_parts.append("鍏呯數琛屼负(+25)") + elif action_type == AGVActionTypeEnum.AVOIDANCE.value: + priority_score += 5.0 + explanation_parts.append("閬胯琛屼负(+5)") + elif action_type == AGVActionTypeEnum.STANDBY.value: + priority_score += 10.0 + explanation_parts.append("寰呮満琛屼负(+10)") + else: + priority_score += 15.0 + explanation_parts.append("鏈煡琛屼负(+15)") + + # 3. 浠诲姟浼樺厛绾� (20%鏉冮噸) + task_priority_score = (task_priority / 10.0) * 20.0 + priority_score += task_priority_score + explanation_parts.append(f"浠诲姟浼樺厛绾task_priority}(+{task_priority_score:.1f})") + + # 4. 鐢甸噺鐘舵�� (10%鏉冮噸) + if voltage <= 10: # 蹇呴』鍏呯數 + priority_score += 10.0 + explanation_parts.append("鐢甸噺鍗遍櫓(+10)") + elif voltage <= 20: # 鍙嚜鍔ㄥ厖鐢� + priority_score += 8.0 + explanation_parts.append("鐢甸噺鍋忎綆(+8)") + elif voltage <= 50: + priority_score += 5.0 + explanation_parts.append("鐢甸噺涓�鑸�(+5)") + else: + priority_score += 2.0 + explanation_parts.append("鐢甸噺鍏呰冻(+2)") + + explanation = f"鎬诲垎{priority_score:.1f}: " + ", ".join(explanation_parts) + + return AGVPriority( + agv_id=agv_id, + priority_score=priority_score, + task_status=task_status, + action_type=action_type, + task_priority=task_priority, + voltage=voltage, + explanation=explanation + ) + + def resolve_conflicts(self, planned_paths: List[Dict], conflicts: List[Conflict], executing_tasks: List[Dict] = None) -> List[Dict]: + """ + 瑙e喅鍐茬獊 + + Args: + planned_paths: 鍘熷瑙勫垝璺緞 + conflicts: 鍐茬獊鍒楄〃 + executing_tasks: 鎵ц涓殑浠诲姟鍒楄〃 + + Returns: + List[Dict]: 瑙e喅鍐茬獊鍚庣殑璺緞 + """ + if not conflicts: + return planned_paths + + resolved_paths = [path.copy() for path in planned_paths] + + # 鎸夋椂闂存鎺掑簭鍐茬獊锛屼紭鍏堣В鍐虫棭鏈熷啿绐� + conflicts_sorted = sorted(conflicts, key=lambda c: c.time_step) + + for conflict in conflicts_sorted: + resolved_paths = self._resolve_single_conflict(resolved_paths, conflict, executing_tasks) + + self.logger.info(f"瑙e喅浜� {len(conflicts)} 涓矾寰勫啿绐�") + return resolved_paths + + def _resolve_single_conflict(self, paths: List[Dict], conflict: Conflict, executing_tasks: List[Dict] = None) -> List[Dict]: + """ + 瑙e喅鍗曚釜鍐茬獊 + + Args: + paths: 褰撳墠璺緞鍒楄〃 + conflict: 鍐茬獊 + executing_tasks: 鎵ц涓殑浠诲姟鍒楄〃 + + Returns: + List[Dict]: 鏇存柊鍚庣殑璺緞鍒楄〃 + """ + if conflict.type == "vertex": + return self._resolve_vertex_conflict(paths, conflict, executing_tasks) + elif conflict.type == "edge": + return self._resolve_edge_conflict(paths, conflict, executing_tasks) + elif conflict.type == "follow": + return self._resolve_following_conflict(paths, conflict, executing_tasks) + + return paths + + def _resolve_vertex_conflict(self, paths: List[Dict], conflict: Conflict, executing_tasks: List[Dict] = None) -> List[Dict]: + """瑙e喅椤剁偣鍐茬獊 - 鍩轰簬浼樺厛绾ц瘎浼伴�夋嫨璁╂鐨凙GV""" + updated_paths = paths.copy() + + # 鎵惧埌鍐茬獊鐨凙GV璺緞 + agv1_path = None + agv2_path = None + agv1_index = agv2_index = -1 + + for i, path in enumerate(updated_paths): + if path.get('agvId') == conflict.agv1: + agv1_path = path + agv1_index = i + elif path.get('agvId') == conflict.agv2: + agv2_path = path + agv2_index = i + + if not agv1_path or not agv2_path: + return updated_paths + + # 璇勪及涓や釜AGV鐨勪紭鍏堢骇 + agv1_priority = self.evaluate_agv_priority(conflict.agv1, agv1_path, executing_tasks) + agv2_priority = self.evaluate_agv_priority(conflict.agv2, agv2_path, executing_tasks) + + # 閫夋嫨浼樺厛绾ц緝浣庣殑AGV杩涜绛夊緟 + if agv1_priority.priority_score <= agv2_priority.priority_score: + waiting_agv_path = agv1_path + waiting_agv_index = agv1_index + waiting_agv_id = conflict.agv1 + higher_priority_agv_id = conflict.agv2 + else: + waiting_agv_path = agv2_path + waiting_agv_index = agv2_index + waiting_agv_id = conflict.agv2 + higher_priority_agv_id = conflict.agv1 + + # 璁板綍璇︾粏鐨勯伩璁╁喅绛栦俊鎭� + self.logger.info(f"椤剁偣鍐茬獊閬胯鍐崇瓥 - 浣嶇疆: {conflict.position1}, 鏃堕棿: {conflict.time_step}") + self.logger.info(f" AGV {conflict.agv1}: {agv1_priority.explanation}") + self.logger.info(f" AGV {conflict.agv2}: {agv2_priority.explanation}") + self.logger.info(f" 鍐崇瓥: AGV {waiting_agv_id} 璁╂缁� AGV {higher_priority_agv_id}") + + # 鍦ㄥ啿绐佷綅缃箣鍓嶆彃鍏ョ瓑寰呮楠� + self._insert_wait_step(waiting_agv_path, conflict.time_step) + updated_paths[waiting_agv_index] = waiting_agv_path + + return updated_paths + + def _resolve_edge_conflict(self, paths: List[Dict], conflict: Conflict, executing_tasks: List[Dict] = None) -> List[Dict]: + """瑙e喅杈瑰啿绐� - 鍩轰簬浼樺厛绾ц瘎浼伴�夋嫨璁╂鐨凙GV""" + updated_paths = paths.copy() + + # 鎵惧埌鍐茬獊鐨凙GV璺緞 + agv1_path = None + agv2_path = None + agv1_index = agv2_index = -1 + + for i, path in enumerate(updated_paths): + if path.get('agvId') == conflict.agv1: + agv1_path = path + agv1_index = i + elif path.get('agvId') == conflict.agv2: + agv2_path = path + agv2_index = i + + if not agv1_path or not agv2_path: + return updated_paths + + # 璇勪及涓や釜AGV鐨勪紭鍏堢骇 + agv1_priority = self.evaluate_agv_priority(conflict.agv1, agv1_path, executing_tasks) + agv2_priority = self.evaluate_agv_priority(conflict.agv2, agv2_path, executing_tasks) + + # 閫夋嫨浼樺厛绾ц緝浣庣殑AGV杩涜绛夊緟 + if agv1_priority.priority_score <= agv2_priority.priority_score: + waiting_agv_path = agv1_path + waiting_agv_index = agv1_index + waiting_agv_id = conflict.agv1 + higher_priority_agv_id = conflict.agv2 + else: + waiting_agv_path = agv2_path + waiting_agv_index = agv2_index + waiting_agv_id = conflict.agv2 + higher_priority_agv_id = conflict.agv1 + + # 璁板綍璇︾粏鐨勯伩璁╁喅绛栦俊鎭� + self.logger.info(f"杈瑰啿绐侀伩璁╁喅绛� - 浣嶇疆浜ゆ崲: {conflict.position1}<->{conflict.position2}, 鏃堕棿: {conflict.time_step}") + self.logger.info(f" AGV {conflict.agv1}: {agv1_priority.explanation}") + self.logger.info(f" AGV {conflict.agv2}: {agv2_priority.explanation}") + self.logger.info(f" 鍐崇瓥: AGV {waiting_agv_id} 璁╂缁� AGV {higher_priority_agv_id}") + + # 鍦ㄥ啿绐佷綅缃箣鍓嶆彃鍏ョ瓑寰呮楠� + self._insert_wait_step(waiting_agv_path, conflict.time_step) + updated_paths[waiting_agv_index] = waiting_agv_path + + return updated_paths + + def _resolve_following_conflict(self, paths: List[Dict], conflict: Conflict, executing_tasks: List[Dict] = None) -> List[Dict]: + """瑙e喅璺熼殢鍐茬獊 - 鍩轰簬浼樺厛绾ц瘎浼伴�夋嫨璁╂鐨凙GV""" + updated_paths = paths.copy() + + # 鎵惧埌鍐茬獊鐨凙GV璺緞 + agv1_path = None + agv2_path = None + agv1_index = agv2_index = -1 + + for i, path in enumerate(updated_paths): + if path.get('agvId') == conflict.agv1: + agv1_path = path + agv1_index = i + elif path.get('agvId') == conflict.agv2: + agv2_path = path + agv2_index = i + + if not agv1_path or not agv2_path: + return updated_paths + + # 璇勪及涓や釜AGV鐨勪紭鍏堢骇 + agv1_priority = self.evaluate_agv_priority(conflict.agv1, agv1_path, executing_tasks) + agv2_priority = self.evaluate_agv_priority(conflict.agv2, agv2_path, executing_tasks) + + # 閫夋嫨浼樺厛绾ц緝浣庣殑AGV杩涜绛夊緟 + if agv1_priority.priority_score <= agv2_priority.priority_score: + waiting_agv_path = agv1_path + waiting_agv_index = agv1_index + waiting_agv_id = conflict.agv1 + higher_priority_agv_id = conflict.agv2 + else: + waiting_agv_path = agv2_path + waiting_agv_index = agv2_index + waiting_agv_id = conflict.agv2 + higher_priority_agv_id = conflict.agv1 + + # 璁板綍璇︾粏鐨勯伩璁╁喅绛栦俊鎭� + self.logger.info(f"璺熼殢鍐茬獊閬胯鍐崇瓥 - 璺濈杩囪繎, 鏃堕棿: {conflict.time_step}") + self.logger.info(f" AGV {conflict.agv1} (浣嶇疆: {conflict.position1}): {agv1_priority.explanation}") + self.logger.info(f" AGV {conflict.agv2} (浣嶇疆: {conflict.position2}): {agv2_priority.explanation}") + self.logger.info(f" 鍐崇瓥: AGV {waiting_agv_id} 璁╂缁� AGV {higher_priority_agv_id}") + + # 鍦ㄥ啿绐佷綅缃箣鍓嶆彃鍏ョ瓑寰呮楠� + self._insert_wait_step(waiting_agv_path, conflict.time_step) + updated_paths[waiting_agv_index] = waiting_agv_path + + return updated_paths + + def _insert_wait_step(self, path: Dict, time_step: int): + """ + 鍦ㄦ寚瀹氭椂闂存鎻掑叆绛夊緟姝ラ + + Args: + path: AGV璺緞 + time_step: 鎻掑叆浣嶇疆鐨勬椂闂存 + """ + code_list = path.get('codeList', []) + + if time_step < len(code_list) and time_step > 0: + # 鍦ㄦ寚瀹氫綅缃彃鍏ョ瓑寰呮楠わ紙閲嶅鍓嶄竴涓綅缃級 + prev_code = code_list[time_step - 1] + + # 纭繚澶嶅埗鎵�鏈夊瓧娈� + if isinstance(prev_code, dict): + wait_code = prev_code.copy() + else: + wait_code = { + 'code': prev_code.code, + 'direction': prev_code.direction + } + + code_list.insert(time_step, wait_code) + path['codeList'] = code_list + + def validate_four_direction_movement(self, planned_paths: List[Dict]) -> List[Dict]: + """ + 楠岃瘉骞朵慨姝h矾寰勶紝纭繚AGV鍙兘浠�0銆�90銆�180銆�270搴︾Щ鍔� + + Args: + planned_paths: 瑙勫垝璺緞鍒楄〃 + + Returns: + List[Dict]: 淇鍚庣殑璺緞鍒楄〃 + """ + validated_paths = [] + + for path_data in planned_paths: + validated_path = self._validate_single_path_directions(path_data) + validated_paths.append(validated_path) + + return validated_paths + + def _validate_single_path_directions(self, path_data: Dict) -> Dict: + """ + 楠岃瘉鍗曚釜璺緞鐨勭Щ鍔ㄦ柟鍚� + + Args: + path_data: 鍗曚釜AGV璺緞鏁版嵁 + + Returns: + Dict: 淇鍚庣殑璺緞鏁版嵁 + """ + validated_path = path_data.copy() + code_list = path_data.get('codeList', []) + + if len(code_list) < 2: + return validated_path + + validated_code_list = [] + + for i in range(len(code_list)): + current_code = code_list[i] + + if isinstance(current_code, dict): + position = current_code.get('code', '') + direction = current_code.get('direction', '90') + + # 淇濈暀鍘熸湁鐨勬墍鏈夊瓧娈� + validated_code = current_code.copy() + else: + position = current_code.code + direction = current_code.direction + + # 杞崲涓哄瓧鍏告牸寮忓苟淇濈暀鍩烘湰瀛楁 + validated_code = { + 'code': position, + 'direction': direction + } + + # 濡傛灉涓嶆槸绗竴涓偣锛岃绠楁纭殑鏂瑰悜 + if i > 0: + prev_code = code_list[i - 1] + prev_position = prev_code.get('code', '') if isinstance(prev_code, dict) else prev_code.code + + # 璁$畻绉诲姩鏂瑰悜 + correct_direction = self._calculate_movement_direction(prev_position, position) + if correct_direction: + direction = correct_direction + + # 纭繚鏂瑰悜鏄湁鏁堢殑鍥涙柟鍚戜箣涓� + direction = self._normalize_direction(direction) + + # 鏇存柊鏂瑰悜锛屼繚鐣欏叾浠栨墍鏈夊瓧娈� + validated_code['direction'] = direction + validated_code_list.append(validated_code) + + validated_path['codeList'] = validated_code_list + return validated_path + + def _calculate_movement_direction(self, from_position: str, to_position: str) -> Optional[str]: + """ + 璁$畻浠庝竴涓綅缃埌鍙︿竴涓綅缃殑绉诲姩鏂瑰悜 + + Args: + from_position: 璧峰浣嶇疆鐮� + to_position: 鐩爣浣嶇疆鐮� + + Returns: + Optional[str]: 绉诲姩鏂瑰悜锛�0, 90, 180, 270锛� + """ + from_coord = get_coordinate_from_path_id(from_position, self.path_mapping) + to_coord = get_coordinate_from_path_id(to_position, self.path_mapping) + + if not from_coord or not to_coord: + return None + + dx = to_coord[0] - from_coord[0] + dy = to_coord[1] - from_coord[1] + + # 鍙厑璁稿洓涓柟鍚戠殑绉诲姩 + if dx > 0 and dy == 0: + return "0" # 鍚戜笢 + elif dx < 0 and dy == 0: + return "180" # 鍚戣タ + elif dx == 0 and dy > 0: + return "90" # 鍚戝崡 + elif dx == 0 and dy < 0: + return "270" # 鍚戝寳 + else: + # 涓嶆槸鏍囧噯鍥涙柟鍚戠Щ鍔紝杩斿洖榛樿鏂瑰悜 + return "90" + + def _normalize_direction(self, direction: str) -> str: + """ + 鏍囧噯鍖栨柟鍚戝�硷紝纭繚鍙湁0銆�90銆�180銆�270 + + Args: + direction: 鍘熷鏂瑰悜鍊� + + Returns: + str: 鏍囧噯鍖栧悗鐨勬柟鍚戝�� + """ + try: + angle = int(direction) % 360 + + # 灏嗚搴︽槧灏勫埌鏈�杩戠殑鍥涙柟鍚� + if angle <= 45 or angle > 315: + return "0" + elif 45 < angle <= 135: + return "90" + elif 135 < angle <= 225: + return "180" + else: + return "270" + except: + return "90" # 榛樿鏂瑰悜 \ No newline at end of file diff --git a/algorithm_system/algorithms/path_planning.py b/algorithm_system/algorithms/path_planning.py new file mode 100644 index 0000000..8820f7a --- /dev/null +++ b/algorithm_system/algorithms/path_planning.py @@ -0,0 +1,1131 @@ +""" +璺緞瑙勫垝绠楁硶 +""" +import heapq +import random +import math +import time +import logging +from typing import Dict, List, Tuple, Optional, Set, Any +from collections import defaultdict, deque + +from common.data_models import PlannedPath, PathCode, AGVStatus, BackpackData +from common.utils import get_coordinate_from_path_id, calculate_distance, calculate_manhattan_distance, generate_segment_id, generate_navigation_code +from algorithm_system.algorithms.collision_detection import CollisionDetector, CollisionResolver + + +class ExecutingTaskExtractor: + """鎵ц涓换鍔℃彁鍙栧櫒""" + + def __init__(self, path_mapping: Dict[str, Dict[str, int]]): + """ + 鍒濆鍖栦换鍔℃彁鍙栧櫒 + + Args: + path_mapping: 璺緞鐐规槧灏勫瓧鍏� + """ + self.path_mapping = path_mapping + self.logger = logging.getLogger(__name__) + + # 棰勮绠楀潗鏍囨槧灏� + self.coord_to_code = {(data['x'], data['y']): code for code, data in path_mapping.items()} + + # 棰勫垎绫讳綅缃被鍨� + self.pickup_positions = [pos for pos in path_mapping.keys() if pos.startswith("1")] + self.charging_positions = [pos for pos in path_mapping.keys() if pos.startswith("2")] + self.delivery_positions = [pos for pos in path_mapping.keys() if pos.startswith("3")] + self.standby_positions = [pos for pos in path_mapping.keys() if pos.startswith("4")] + + def extract_optimized_executing_tasks(self, agv_status_list: List[AGVStatus]) -> List[Dict]: + """鏅鸿兘鎻愬彇AGV鎵ц浠诲姟""" + executing_tasks = [] + + for agv_status in agv_status_list: + agv_id = agv_status.agvId + current_position = agv_status.position + + if not agv_status.backpack: + continue + + optimized_task = self._analyze_all_backpack_tasks( + agv_id, current_position, agv_status.backpack, agv_status + ) + + if optimized_task: + executing_tasks.append(optimized_task) + + self.logger.info(f"鏅鸿兘鍒嗘瀽 {len(executing_tasks)} 涓狝GV鐨勮儗绡撲换鍔″苟鐢熸垚鏈�浼樿矾寰�") + return executing_tasks + + def _analyze_all_backpack_tasks(self, agv_id: str, current_position: str, + backpack: List[BackpackData], agv_status: AGVStatus) -> Optional[Dict]: + """ + 鍒嗘瀽鎵�鏈夎儗绡撲换鍔★紝纭畾鏈�浼樻墽琛岀瓥鐣� + """ + # 1. 鎻愬彇骞跺垎绫绘墍鏈夋湁鏁堜换鍔� + loaded_tasks = [] # 宸茶杞界殑浠诲姟 + unloaded_tasks = [] # 鏈杞界殑浠诲姟 + all_tasks = [] # 鎵�鏈変换鍔� + + self.logger.debug(f"[AGV {agv_id}] 寮�濮嬪垎鏋愯儗绡撲换鍔★紝浣嶇疆: {current_position}") + + for i, bp in enumerate(backpack): + self.logger.debug(f"[AGV {agv_id}] 鑳岀瘬浣嶇疆{i+1}: taskId={bp.taskId}, loaded={bp.loaded}, execute={bp.execute}") + + if not bp.taskId: + self.logger.debug(f"[AGV {agv_id}] 鑳岀瘬浣嶇疆{i+1}: 璺宠繃锛堟棤浠诲姟ID锛�") + continue + + # 灏濊瘯瑙f瀽浠诲姟璇︽儏 + task_info = self._parse_task_details(bp.taskId, current_position) + if not task_info: + self.logger.warning(f"[AGV {agv_id}] 浠诲姟 {bp.taskId} 瑙f瀽澶辫触锛岃烦杩�") + continue + + task_data = { + 'backpack_item': bp, + 'task_info': task_info, + 'distance_to_start': task_info.get('distance_to_start', float('inf')), + 'distance_to_end': task_info.get('distance_to_end', float('inf')) + } + + all_tasks.append(task_data) + + if bp.loaded: + loaded_tasks.append(task_data) + self.logger.debug(f"[AGV {agv_id}] 浠诲姟 {bp.taskId} 宸茶杞斤紝璺濈缁堢偣: {task_info.get('distance_to_end', 'N/A')}") + else: + unloaded_tasks.append(task_data) + self.logger.debug(f"[AGV {agv_id}] 浠诲姟 {bp.taskId} 鏈杞斤紝璺濈璧风偣: {task_info.get('distance_to_start', 'N/A')}") + + self.logger.debug(f"[AGV {agv_id}] 浠诲姟鍒嗘瀽缁撴灉: 鎬讳换鍔�={len(all_tasks)}, 宸茶杞�={len(loaded_tasks)}, 鏈杞�={len(unloaded_tasks)}") + + # 2. 濡傛灉娌℃湁浠讳綍浠诲姟锛岃繑鍥濶one + if not all_tasks: + self.logger.debug(f"[AGV {agv_id}] 娌℃湁鏈夋晥浠诲姟锛岃繑鍥濶one") + return None + + # 3. 鍐崇瓥鏈�浼樿矾寰勭瓥鐣� + optimal_strategy = self._determine_next_best_action( + loaded_tasks, unloaded_tasks, current_position + ) + + if not optimal_strategy: + self.logger.debug(f"[AGV {agv_id}] 鍐崇瓥杩斿洖None锛屾病鏈夋渶浼樼瓥鐣�") + return None + + self.logger.debug(f"[AGV {agv_id}] 鍐崇瓥瀹屾垚: {optimal_strategy['strategy']} - {optimal_strategy['action']}") + self.logger.debug(f"[AGV {agv_id}] 鐩爣浣嶇疆: {optimal_strategy['target_position']}") + self.logger.debug(f"[AGV {agv_id}] 鍐崇瓥鍘熷洜: {optimal_strategy.get('reason', 'N/A')}") + + # 4. 鏋勫缓浠诲姟淇℃伅 + result = { + 'agvId': agv_id, + 'currentPosition': current_position, + 'strategy': optimal_strategy['strategy'], + 'next_action': optimal_strategy['action'], + 'target_position': optimal_strategy['target_position'], + 'primary_task': optimal_strategy['selected_task'], + 'all_loaded_tasks': loaded_tasks, + 'all_unloaded_tasks': unloaded_tasks, + 'total_tasks_count': len(all_tasks), + 'estimated_efficiency': optimal_strategy.get('efficiency_score', 0), + 'decision_reason': optimal_strategy.get('reason', ''), + 'agvDirection': agv_status.direction, + 'agvVoltage': agv_status.vol, + 'agvError': agv_status.error + } + + self.logger.debug(f"[AGV {agv_id}] 鏈�缁堜换鍔′俊鎭瀯寤哄畬鎴愶紝杩斿洖缁撴灉") + return result + + def _determine_next_best_action(self, loaded_tasks: List[Dict], + unloaded_tasks: List[Dict], current_position: str) -> Optional[Dict]: + """ + 纭畾AGV涓嬩竴姝ユ渶浼樿鍔� + + Args: + loaded_tasks: 宸茶杞界殑浠诲姟 + unloaded_tasks: 鏈杞界殑浠诲姟 + current_position: 褰撳墠浣嶇疆 + + Returns: + Optional[Dict]: 鏈�浼樿鍔ㄧ瓥鐣� + """ + # 榛樿绛栫暐锛氱患鍚堜紭鍖栧喅绛� + # 1. 濡傛灉鏈夊凡瑁呰浇浠诲姟锛屼紭鍏堥�佽揣 + if loaded_tasks: + nearest_delivery = min(loaded_tasks, key=lambda t: t['distance_to_end']) + + # 2. 濡傛灉涔熸湁鏈杞戒换鍔★紝姣旇緝璺濈鍐崇瓥 + if unloaded_tasks: + nearest_pickup = min(unloaded_tasks, key=lambda t: t['distance_to_start']) + + # 缁煎悎浼樺寲绛栫暐锛氬鏋滃彇璐х偣鏄捐憲鏇磋繎锛屼紭鍏堝彇璐э紱鍚﹀垯浼樺厛閫佽揣 + if nearest_pickup['distance_to_start'] * 1.5 < nearest_delivery['distance_to_end']: + return { + 'strategy': 'comprehensive_optimization', + 'action': 'pickup', + 'selected_task': nearest_pickup, + 'target_position': nearest_pickup['task_info']['start_position'], + 'efficiency_score': self._calculate_pickup_efficiency(nearest_pickup, current_position), + 'reason': f"缁煎悎浼樺寲锛氬彇璐х偣({nearest_pickup['distance_to_start']})姣旈�佽揣鐐�({nearest_delivery['distance_to_end']})鏄捐憲鏇磋繎" + } + else: + return { + 'strategy': 'comprehensive_optimization', + 'action': 'deliver', + 'selected_task': nearest_delivery, + 'target_position': nearest_delivery['task_info']['end_position'], + 'efficiency_score': self._calculate_delivery_efficiency(nearest_delivery, current_position), + 'reason': f"缁煎悎浼樺寲锛氫紭鍏堥�佽揪宸茶杞借揣鐗╋紝閫佽揣璺濈{nearest_delivery['distance_to_end']}" + } + else: + # 鍙湁宸茶杞戒换鍔★紝鐩存帴閫佽揣 + return { + 'strategy': 'comprehensive_optimization', + 'action': 'deliver', + 'selected_task': nearest_delivery, + 'target_position': nearest_delivery['task_info']['end_position'], + 'efficiency_score': self._calculate_delivery_efficiency(nearest_delivery, current_position), + 'reason': f"缁煎悎浼樺寲锛氶�佽揪鍞竴宸茶杞借揣鐗╋紝璺濈{nearest_delivery['distance_to_end']}" + } + + # 3. 濡傛灉鍙湁鏈杞戒换鍔★紝鍙栬揣 + elif unloaded_tasks: + nearest_pickup = min(unloaded_tasks, key=lambda t: t['distance_to_start']) + return { + 'strategy': 'comprehensive_optimization', + 'action': 'pickup', + 'selected_task': nearest_pickup, + 'target_position': nearest_pickup['task_info']['start_position'], + 'efficiency_score': self._calculate_pickup_efficiency(nearest_pickup, current_position), + 'reason': f"缁煎悎浼樺寲锛氬彇璐у敮涓�鏈杞戒换鍔★紝璺濈{nearest_pickup['distance_to_start']}" + } + + # 4. 娌℃湁浠诲姟 + return None + + def _parse_task_details(self, task_id: str, current_position: str) -> Optional[Dict]: + """ + 瑙f瀽浠诲姟璇︾粏淇℃伅锛屽寘鎷捣缁堢偣鍜岃窛绂� + + Args: + task_id: 浠诲姟ID + current_position: 褰撳墠浣嶇疆 + + Returns: + Optional[Dict]: 浠诲姟璇︾粏淇℃伅 + """ + self.logger.debug(f"寮�濮嬭В鏋愪换鍔� {task_id}锛屽綋鍓嶄綅缃�: {current_position}") + + start_pos, end_pos = self._fast_parse_task_start_end(task_id) + + if not start_pos or not end_pos: + self.logger.warning(f"浠诲姟 {task_id} 瑙f瀽澶辫触: start_pos={start_pos}, end_pos={end_pos}") + return None + + self.logger.debug(f"浠诲姟 {task_id} 瑙f瀽鎴愬姛: start={start_pos}, end={end_pos}") + + # 璁$畻璺濈 + current_coord = get_coordinate_from_path_id(current_position, self.path_mapping) + start_coord = get_coordinate_from_path_id(start_pos, self.path_mapping) + end_coord = get_coordinate_from_path_id(end_pos, self.path_mapping) + + if not all([current_coord, start_coord, end_coord]): + return None + + distance_to_start = calculate_manhattan_distance(current_coord, start_coord) + distance_to_end = calculate_manhattan_distance(current_coord, end_coord) + + return { + 'task_id': task_id, + 'start_position': start_pos, + 'end_position': end_pos, + 'distance_to_start': distance_to_start, + 'distance_to_end': distance_to_end, + 'start_coord': start_coord, + 'end_coord': end_coord + } + + def _calculate_delivery_efficiency(self, task_data: Dict, current_position: str) -> float: + """ + 璁$畻閫佽揣鏁堢巼鍒嗘暟 + + Args: + task_data: 浠诲姟鏁版嵁 + current_position: 褰撳墠浣嶇疆 + + Returns: + float: 鏁堢巼鍒嗘暟 (0-10) + """ + distance = task_data['distance_to_end'] + + # 璺濈瓒婅繎鏁堢巼瓒婇珮 + distance_score = max(0, 10 - distance / 5) + + # 宸茶杞界殑浠诲姟鏈夐澶栧鍔� + loaded_bonus = 3.0 if task_data['backpack_item'].loaded else 0 + + return distance_score + loaded_bonus + + def _calculate_pickup_efficiency(self, task_data: Dict, current_position: str) -> float: + """ + 璁$畻鍙栬揣鏁堢巼鍒嗘暟 + + Args: + task_data: 浠诲姟鏁版嵁 + current_position: 褰撳墠浣嶇疆 + + Returns: + float: 鏁堢巼鍒嗘暟 (0-10) + """ + distance_to_start = task_data['distance_to_start'] + + # 璺濈瓒婅繎鏁堢巼瓒婇珮 + distance_score = max(0, 10 - distance_to_start / 3) + + # 寰堣繎鐨勫彇璐х偣鏈夐珮濂栧姳 + if distance_to_start <= 3: + distance_score += 5.0 + elif distance_to_start <= 5: + distance_score += 2.0 + + return distance_score + + def _fast_parse_task_start_end(self, task_id: str) -> Tuple[Optional[str], Optional[str]]: + """ + 瑙f瀽浠诲姟璧风偣鍜岀粓鐐� + 鏀寔涓ょ鏍煎紡锛� + 1. 鏍囧噯鏍煎紡锛歺xx_start_end + 2. 绠�鍗曟牸寮忥細鏁板瓧ID锛堥�氳繃hash鍒嗛厤浣嶇疆锛� + + Args: + task_id: 浠诲姟ID + + Returns: + Tuple[Optional[str], Optional[str]]: (璧风偣, 缁堢偣) + """ + self.logger.debug(f"瑙f瀽浠诲姟ID: {task_id}") + + # 鏂规硶1锛氬皾璇曟爣鍑嗘牸寮忚В鏋愶紙鍖呭惈涓嬪垝绾匡級 + if "_" in task_id: + parts = task_id.split("_") + self.logger.debug(f"浠诲姟ID鍖呭惈涓嬪垝绾匡紝鍒嗗壊缁撴灉: {parts}") + + if len(parts) >= 3: + potential_start = parts[-2] + potential_end = parts[-1] + + start_valid = potential_start in self.path_mapping + end_valid = potential_end in self.path_mapping + + self.logger.debug(f"鏍囧噯鏍煎紡瑙f瀽缁撴灉: start={potential_start}(valid={start_valid}), end={potential_end}(valid={end_valid})") + + if start_valid and end_valid: + return potential_start, potential_end + + self.logger.debug(f"浠诲姟ID鍒嗗壊鍚庨儴鍒嗘暟閲忎笉瓒�: {len(parts)} < 3") + + # 鏂规硶2锛氱畝鍗曟暟瀛桰D鐨勫閫夋満鍒讹紙閫氳繃hash鍒嗛厤浣嶇疆锛� + self.logger.debug(f"灏濊瘯绠�鍗曟暟瀛桰D澶囬�夎В鏋愭満鍒�") + + # 鑾峰彇鎵�鏈夊彲鐢ㄤ綅缃� + all_positions = list(self.path_mapping.keys()) + if len(all_positions) < 2: + self.logger.warning(f"璺緞鏄犲皠浣嶇疆涓嶈冻锛屾棤娉曚负浠诲姟鍒嗛厤璧风粓鐐�") + return None, None + + # 浣跨敤浠诲姟ID鐨刪ash鍊兼潵鍒嗛厤璧风偣鍜岀粓鐐� + try: + task_hash = abs(hash(task_id)) + + # 鍒嗛厤璧风偣锛堜娇鐢╤ash鐨勫墠涓�鍗婏級 + start_index = task_hash % len(all_positions) + start_pos = all_positions[start_index] + + # 鍒嗛厤缁堢偣锛堜娇鐢╤ash鐨勫悗涓�鍗婏紝纭繚涓庤捣鐐逛笉鍚岋級 + end_index = (task_hash // len(all_positions)) % len(all_positions) + if end_index == start_index: + end_index = (end_index + 1) % len(all_positions) + end_pos = all_positions[end_index] + + self.logger.debug(f"绠�鍗旾D澶囬�夎В鏋愭垚鍔�: 浠诲姟{task_id} -> start={start_pos}, end={end_pos}") + return start_pos, end_pos + + except Exception as e: + self.logger.error(f"绠�鍗旾D澶囬�夎В鏋愬け璐�: {e}") + + self.logger.warning(f"浠诲姟ID {task_id} 鎵�鏈夎В鏋愭柟娉曢兘澶辫触锛岃繑鍥� None, None") + return None, None + + def _fast_parse_target(self, task_id: str, current_position: str, loaded: bool) -> Optional[str]: + """ + 瑙f瀽浠诲姟鐩爣浣嶇疆 + + Args: + task_id: 浠诲姟ID + current_position: 褰撳墠浣嶇疆 + loaded: 鏄惁宸茶杞� + + Returns: + Optional[str]: 鐩爣浣嶇疆鐮� + """ + _, task_end = self._fast_parse_task_start_end(task_id) + if task_end: + return task_end + + # 鍩轰簬hash鍊煎拰瑁呰浇鐘舵�佸揩閫熼�夋嫨 + hash_val = hash(task_id) % 1000 + + if loaded: + # 宸茶杞斤紝閫夋嫨鍑哄簱鍖哄煙 + target_positions = [pos for pos in self.delivery_positions if pos != current_position] + if not target_positions: + target_positions = [pos for pos in self.path_mapping.keys() if pos != current_position] + else: + # 鏈杞斤紝閫夋嫨浠撳簱鍖哄煙 + target_positions = [pos for pos in self.pickup_positions if pos != current_position] + if not target_positions: + target_positions = [pos for pos in self.path_mapping.keys() if pos != current_position] + + if target_positions: + return target_positions[hash_val % len(target_positions)] + + return None + + def extract_executing_tasks(self, agv_status_list: List[AGVStatus]) -> List[Dict]: + """鎻愬彇鎵�鏈堿GV姝e湪鎵ц鐨勪换鍔�""" + self.logger.debug(f"寮�濮嬭浆鎹换鍔′负鏍囧噯鏍煎紡锛孉GV鏁伴噺: {len(agv_status_list)}") + + optimized_tasks = self.extract_optimized_executing_tasks(agv_status_list) + + self.logger.debug(f"鎻愬彇缁撴灉鏁伴噺: {len(optimized_tasks)}") + + legacy_tasks = [] + for i, task in enumerate(optimized_tasks): + try: + self.logger.debug(f"杞崲浠诲姟 #{i+1}: AGV {task['agvId']}") + + primary_task = task['primary_task'] + task_info = primary_task['task_info'] + backpack_item = primary_task['backpack_item'] + + self.logger.debug(f" - primary_task瀛樺湪: {primary_task is not None}") + self.logger.debug(f" - task_info瀛樺湪: {task_info is not None}") + self.logger.debug(f" - backpack_item瀛樺湪: {backpack_item is not None}") + + # 鏍规嵁action纭畾鐘舵�� + is_pickup_action = task['next_action'] == 'pickup' + + self.logger.debug(f" - next_action: {task['next_action']}") + self.logger.debug(f" - is_pickup_action: {is_pickup_action}") + self.logger.debug(f" - target_position: {task['target_position']}") + + # 淇濇寔鍘熸湁鐨勬爣鍑咼SON鏍煎紡 + legacy_task = { + 'agvId': task['agvId'], + 'taskId': task_info['task_id'], + 'currentPosition': task['currentPosition'], + 'backpackIndex': backpack_item.index, + 'status': 'executing', + 'agvDirection': task['agvDirection'], + 'agvVoltage': task['agvVoltage'], + 'agvError': task['agvError'], + 'loaded': backpack_item.loaded, + 'needPickup': is_pickup_action, + 'targetPosition': task['target_position'], + 'taskStart': task_info['start_position'], + 'taskEnd': task_info['end_position'] + } + + self.logger.debug(f" - 浠诲姟鏋勫缓鎴愬姛: {legacy_task['agvId']} -> {legacy_task['targetPosition']}") + legacy_tasks.append(legacy_task) + + except Exception as e: + self.logger.error(f"杞崲浠诲姟 #{i+1} 澶辫触: {e}") + self.logger.error(f"浠诲姟鍐呭: {task}") + continue + + self.logger.info(f"浣跨敤缁煎悎浼樺寲绛栫暐锛屼负 {len(legacy_tasks)} 涓狝GV鐢熸垚鏍囧噯鏍煎紡璺緞") + return legacy_tasks + + +class BatchPathPlanner: + """鎵归噺璺緞瑙勫垝鍣�""" + + def __init__(self, path_mapping: Dict[str, Dict[str, int]], + algorithm_type: str = "A_STAR", agv_manager=None): + """ + 鍒濆鍖栨壒閲忚矾寰勮鍒掑櫒 + + Args: + path_mapping: 璺緞鐐规槧灏勫瓧鍏� + algorithm_type: 鍩虹璺緞瑙勫垝绠楁硶绫诲瀷 + agv_manager: 鐢ㄤ簬鑾峰彇AGV鐘舵�佷俊鎭� + """ + self.path_mapping = path_mapping + self.algorithm_type = algorithm_type + self.agv_manager = agv_manager + self.logger = logging.getLogger(__name__) + + # 鍒濆鍖栦紭鍖栫粍浠� + self.task_extractor = ExecutingTaskExtractor(path_mapping) + self.collision_detector = FastCollisionDetector(min_distance=3.0) + self.collision_resolver = FastCollisionResolver(path_mapping, self.collision_detector, agv_manager) + self.base_planner = PathPlanningFactory.create_path_planner(algorithm_type, path_mapping) + + def plan_all_agv_paths(self, agv_status_list: List[AGVStatus], + include_idle_agv: bool = False, + constraints: List[Tuple[int, int, float]] = None) -> Dict: + """ + 涓烘墍鏈堿GV瑙勫垝璺緞锛堟瘡涓狝GV鍙敓鎴愪竴涓矾寰勶級 + + Args: + agv_status_list: AGV鐘舵�佸垪琛� + include_idle_agv: 鏄惁鍖呭惈绌洪棽AGV + constraints: 璺緞绾︽潫鏉′欢 + + Returns: + Dict: 鍖呭惈鎵�鏈夎矾寰勪俊鎭殑缁撴灉 + """ + start_time = time.time() + + # 1. 鎻愬彇鎵ц涓殑浠诲姟锛堜娇鐢ㄥ悜鍚庡吋瀹圭殑鏂规硶锛� + executing_tasks = self.task_extractor.extract_executing_tasks(agv_status_list) + + # 2. 璺緞瑙勫垝 + planned_paths = [] + total_planning_time = 0 + planned_agv_ids = set() + + for task in executing_tasks: + agv_id = task['agvId'] + current_pos = task['currentPosition'] + target_pos = task['targetPosition'] + task_id = task['taskId'] + + self.logger.debug(f"寮�濮嬭矾寰勮鍒�: AGV {agv_id}, 浠诲姟 {task_id}") + self.logger.debug(f" - current_pos: {current_pos} ({type(current_pos)})") + self.logger.debug(f" - target_pos: {target_pos} ({type(target_pos)})") + + # 閬垮厤閲嶅瑙勫垝 + if agv_id in planned_agv_ids: + self.logger.debug(f" - 璺宠繃AGV {agv_id}锛氬凡瑙勫垝") + continue + + # 楠岃瘉浣嶇疆鏍煎紡 + if not current_pos or not target_pos: + self.logger.error(f" - AGV {agv_id} 浣嶇疆淇℃伅鏃犳晥: current={current_pos}, target={target_pos}") + continue + + # 璺緞鏍煎紡鏍囧噯鍖� + from common.utils import normalize_path_id + normalized_current = normalize_path_id(current_pos) + normalized_target = normalize_path_id(target_pos) + + self.logger.debug(f" - 鏍煎紡鏍囧噯鍖�: {current_pos} -> {normalized_current}, {target_pos} -> {normalized_target}") + + # 璺緞瑙勫垝 + path_start_time = time.time() + self.logger.debug(f" - 璋冪敤base_planner.plan_path({normalized_current}, {normalized_target})") + planned_path = self.base_planner.plan_path(normalized_current, normalized_target, constraints) + path_end_time = time.time() + + planning_time = (path_end_time - path_start_time) * 1000 + total_planning_time += planning_time + + self.logger.debug(f" - 璺緞瑙勫垝缁撴灉: {planned_path is not None}") + if planned_path: + self.logger.debug(f" - 璺緞闀垮害: {len(planned_path.codeList) if hasattr(planned_path, 'codeList') else 'N/A'}") + else: + self.logger.error(f" - AGV {agv_id} 璺緞瑙勫垝澶辫触: {current_pos} -> {target_pos}") + + if planned_path: + # 鐢熸垚璺緞瀛楀吀 + seg_id = generate_segment_id(agv_id=agv_id, task_id=task_id, action_type="2") + + path_dict = { + 'agvId': agv_id, + 'segId': seg_id, + 'codeList': [] + } + + # 杞崲璺緞鐐规牸寮� + for i, path_code in enumerate(planned_path.codeList): + if isinstance(path_code, PathCode): + action_type = "2" # 浠诲姟绫诲瀷 + pos_type = None + backpack_level = task.get('backpackIndex', 0) + is_target_point = False + + # 纭畾浣嶇疆绫诲瀷锛氭渶鍚庝竴涓偣鐨勫姩浣滅被鍨� + if i == len(planned_path.codeList) - 1: # 鏈�鍚庝竴涓矾寰勭偣 + is_target_point = True + if task.get('loaded', False): + # 宸茶杞戒换鍔★細褰撳墠璺緞鏄幓缁堢偣鏀捐揣 + pos_type = "2" # 鏀捐揣 + self.logger.debug(f"AGV {agv_id} 鍦ㄧ粓鐐� {path_code.code} 璁剧疆鏀捐揣鍔ㄤ綔") + else: + # 鏈杞戒换鍔★細褰撳墠璺緞鏄幓璧风偣鍙栬揣 + pos_type = "1" # 鍙栬揣 + self.logger.debug(f"AGV {agv_id} 鍦ㄨ捣鐐� {path_code.code} 璁剧疆鍙栬揣鍔ㄤ綔") + + enhanced_code = generate_navigation_code( + code=path_code.code, + direction=path_code.direction, + action_type=action_type, + task_id=task_id, + pos_type=pos_type, + backpack_level=backpack_level, + is_target_point=is_target_point + ) + path_dict['codeList'].append(enhanced_code) + else: + enhanced_code = generate_navigation_code( + code=path_code.get('code', ''), + direction=path_code.get('direction', '90'), + action_type="2", + task_id=task_id + ) + path_dict['codeList'].append(enhanced_code) + + planned_paths.append(path_dict) + planned_agv_ids.add(agv_id) + self.logger.debug(f"AGV {agv_id} 蹇�熻鍒�: {current_pos} -> {target_pos}") + + # 3. 绌洪棽AGV澶勭悊 + if include_idle_agv: + idle_paths = self._fast_plan_idle_agv_paths(agv_status_list, executing_tasks, constraints, planned_agv_ids) + planned_paths.extend(idle_paths) + + # 4. 鍘婚噸妫�鏌� + final_paths = [] + final_agv_ids = set() + for path in planned_paths: + agv_id = path['agvId'] + if agv_id not in final_agv_ids: + final_paths.append(path) + final_agv_ids.add(agv_id) + planned_paths = final_paths + + # 5. 纰版挒妫�娴嬪拰瑙e喅 + conflicts = self.collision_detector.detect_conflicts(planned_paths) + + if conflicts: + self.logger.info(f"鍙戠幇 {len(conflicts)} 涓鎾烇紝蹇�熻В鍐充腑...") + planned_paths = self.collision_resolver.resolve_conflicts(planned_paths, conflicts, executing_tasks) + remaining_conflicts = self.collision_detector.detect_conflicts(planned_paths) + else: + remaining_conflicts = [] + + end_time = time.time() + total_time = (end_time - start_time) * 1000 + + # 6. 鏋勫缓浼樺寲缁撴灉 + result = { + 'totalAgvs': len(agv_status_list), + 'executingTasksCount': len(executing_tasks), + 'plannedPathsCount': len(planned_paths), + 'totalPlanningTime': round(total_time, 2), + 'pathPlanningTime': round(total_planning_time, 2), + 'conflictsDetected': len(conflicts), + 'remainingConflicts': len(remaining_conflicts), + 'algorithm': f"Optimized_{self.algorithm_type}", + 'fourDirectionCompliant': True, + 'collisionFree': len(remaining_conflicts) == 0, + 'plannedPaths': planned_paths, + 'executingTasks': executing_tasks + } + + self.logger.info(f"浼樺寲鎵归噺璺緞瑙勫垝瀹屾垚 - 鎬籄GV: {result['totalAgvs']}, " + f"鎵ц涓换鍔�: {result['executingTasksCount']}, " + f"瑙勫垝璺緞: {result['plannedPathsCount']}, " + f"鎬昏�楁椂: {result['totalPlanningTime']:.2f}ms, " + f"鏃犵鎾�: {result['collisionFree']}") + + return result + + def _fast_plan_idle_agv_paths(self, agv_status_list: List[AGVStatus], + executing_tasks: List[Dict], + constraints: List[Tuple[int, int, float]] = None, + planned_agv_ids: Set[str] = None) -> List[Dict]: + """ + 涓虹┖闂睞GV瑙勫垝璺緞 + """ + idle_paths = [] + executing_agv_ids = {task['agvId'] for task in executing_tasks} + + if planned_agv_ids is None: + planned_agv_ids = executing_agv_ids.copy() + + for agv_status in agv_status_list: + agv_id = agv_status.agvId + + if agv_id in planned_agv_ids: + continue + + if self._is_agv_idle_fast(agv_status): + current_pos = agv_status.position + target_pos = self._get_idle_agv_target_fast(agv_id, current_pos) + + if target_pos and target_pos != current_pos: + planned_path = self.base_planner.plan_path(current_pos, target_pos, constraints) + + if planned_path: + action_type = "3" if self._is_charging_path(target_pos) else "4" + seg_id = generate_segment_id(agv_id=agv_id, target_position=target_pos, action_type=action_type) + + path_dict = { + 'agvId': agv_id, + 'segId': seg_id, + 'codeList': [] + } + + for i, path_code in enumerate(planned_path.codeList): + if isinstance(path_code, PathCode): + # 绌洪棽AGV涓嶉渶瑕乼askId銆乸osType鍜宭ev瀛楁 + enhanced_code = generate_navigation_code( + code=path_code.code, + direction=path_code.direction, + action_type=action_type, + is_target_point=False # 绌洪棽AGV璺緞鏃犵洰鏍囩偣姒傚康 + ) + path_dict['codeList'].append(enhanced_code) + + idle_paths.append(path_dict) + planned_agv_ids.add(agv_id) + + return idle_paths + + def _is_agv_idle_fast(self, agv_status: AGVStatus) -> bool: + """鍒ゆ柇AGV鏄惁绌洪棽""" + if agv_status.status not in [0, 1, 2] or agv_status.error != 0: + return False + + # 妫�鏌ヨ儗绡� + if agv_status.backpack: + for item in agv_status.backpack[:2]: # 鍙鏌ュ墠涓や釜 + if item.loaded and item.execute: + return False + + return True + + def _get_idle_agv_target_fast(self, agv_id: str, current_position: str) -> Optional[str]: + """鑾峰彇绌洪棽AGV鐩爣浣嶇疆""" + hash_val = hash(agv_id) % 1000 + + # 閫夋嫨閫昏緫 + charging_positions = [pos for pos in self.task_extractor.charging_positions if pos != current_position] + if charging_positions: + return charging_positions[hash_val % len(charging_positions)] + + standby_positions = [pos for pos in self.task_extractor.standby_positions if pos != current_position] + if standby_positions: + return standby_positions[hash_val % len(standby_positions)] + + all_positions = [pos for pos in self.path_mapping.keys() if pos != current_position] + if all_positions: + return all_positions[hash_val % len(all_positions)] + + return None + + def _is_charging_path(self, target_position: str) -> bool: + """ + 鍒ゆ柇鐩爣浣嶇疆鏄惁涓哄厖鐢电珯锛堝悗鏈熶慨鏀癸級 + """ + if not target_position: + return False + + # 鍏呯數绔欒瘑鍒� + return target_position.startswith("2") or "charge" in target_position.lower() + + +class PathPlanner: + """璺緞瑙勫垝鍣ㄥ熀绫�""" + + def __init__(self, path_mapping: Dict[str, Dict[str, int]]): + """ + 鍒濆鍖栬矾寰勮鍒掑櫒 + + Args: + path_mapping: 璺緞鐐规槧灏勫瓧鍏� + """ + self.path_mapping = path_mapping + self.logger = logging.getLogger(__name__) + + def plan_path(self, start_code: str, end_code: str, + constraints: List[Tuple[int, int, float]] = None) -> Optional[PlannedPath]: + """ + 瑙勫垝浠庤捣鐐瑰埌缁堢偣鐨勮矾寰� + + Args: + start_code: 璧峰璺緞鐐笽D + end_code: 鐩爣璺緞鐐笽D + constraints: 绾︽潫鏉′欢鍒楄〃 + + Returns: + Optional[PlannedPath]: 瑙勫垝鐨勮矾寰勶紝濡傛灉鏃犳硶瑙勫垝鍒欒繑鍥濶one + """ + raise NotImplementedError("瀛愮被蹇呴』瀹炵幇姝ゆ柟娉�") + + def is_valid_path_point(self, code: str) -> bool: + """ + 妫�鏌ヨ矾寰勭偣ID鏄惁鏈夋晥 + + Args: + code: 璺緞鐐笽D + + Returns: + bool: 鏄惁鏈夋晥 + """ + return code in self.path_mapping + + +class AStarPathPlanner(PathPlanner): + """A*璺緞瑙勫垝鍣�""" + + def __init__(self, path_mapping: Dict[str, Dict[str, int]], turn_cost: float = 0.5): + """ + 鍒濆鍖朅*璺緞瑙勫垝鍣� + + Args: + path_mapping: 璺緞鐐规槧灏勫瓧鍏� + turn_cost: 杞悜浠d环 + """ + super().__init__(path_mapping) + self.turn_cost = turn_cost + self.logger = logging.getLogger(__name__) + + # 鍥涗釜鏂瑰悜锛氫笂銆佸彸銆佷笅銆佸乏 (涓ユ牸鍥涙柟鍚戠Щ鍔�) + self.directions = [(0, -1), (1, 0), (0, 1), (-1, 0)] + self.direction_angles = ["270", "0", "90", "180"] + + # 棰勮绠楀潗鏍囨槧灏勶紙蹇呴』鍦� _build_adjacency_list 涔嬪墠鍒濆鍖栵級 + self.coord_to_code = {} + for code, data in path_mapping.items(): + if isinstance(data, dict) and 'x' in data and 'y' in data: + self.coord_to_code[(data['x'], data['y'])] = code + + # 棰勮绠楅偦鎺ヨ〃 + self.adjacency = self._build_adjacency_list() + + # 鎬ц兘鐩戞帶 + self.planning_count = 0 + self.total_planning_time = 0.0 + + self.logger.debug(f"A*瑙勫垝鍣ㄥ垵濮嬪寲瀹屾垚锛岄偦鎺ヨ〃: {len(self.adjacency)} 涓妭鐐�") + + def _build_adjacency_list(self) -> Dict[str, List[Tuple[str, str]]]: + """ + 棰勮绠楅偦鎺ヨ〃 + + Returns: + Dict[str, List[Tuple[str, str]]]: {鑺傜偣: [(閭诲眳鑺傜偣, 绉诲姩鏂瑰悜), ...]} + """ + adjacency = defaultdict(list) + + for code, coord_data in self.path_mapping.items(): + x, y = coord_data['x'], coord_data['y'] + + # 妫�鏌ュ洓涓柟鍚戠殑閭诲眳 + for i, (dx, dy) in enumerate(self.directions): + next_x, next_y = x + dx, y + dy + + # 鏌ユ壘閭诲眳浠g爜 + neighbor_coord = (next_x, next_y) + if neighbor_coord in self.coord_to_code: + neighbor_code = self.coord_to_code[neighbor_coord] + direction = self.direction_angles[i] + adjacency[code].append((neighbor_code, direction)) + + return adjacency + + def plan_path(self, start_code: str, end_code: str, + constraints: List[Tuple[int, int, float]] = None) -> Optional[PlannedPath]: + """ + Args: + start_code: 璧峰璺緞鐐笽D + end_code: 鐩爣璺緞鐐笽D + constraints: 绾︽潫鏉′欢鍒楄〃 + + Returns: + Optional[PlannedPath]: 瑙勫垝鐨勮矾寰� + """ + # 楠岃瘉 + if not self.is_valid_path_point(start_code) or not self.is_valid_path_point(end_code): + return None + + if start_code == end_code: + return PlannedPath(agvId="", codeList=[PathCode(code=start_code, direction="90")]) + + # 鑾峰彇鍧愭爣锛堜娇鐢ㄩ璁$畻鏄犲皠锛� + start_coord = (self.path_mapping[start_code]['x'], self.path_mapping[start_code]['y']) + end_coord = (self.path_mapping[end_code]['x'], self.path_mapping[end_code]['y']) + + # A*瀹炵幇 + # 浣跨敤鍏冪粍鍫嗭紝鍑忓皯瀵硅薄鍒涘缓寮�閿� + open_set = [(0, start_code, None, [])] # (f_score, code, parent, path) + closed_set = set() + g_scores = {start_code: 0} + + # 棰勮绠楃害鏉熸鏌ュ嚱鏁� + constraint_check = self._build_constraint_checker(constraints) if constraints else None + + while open_set: + f_score, current_code, parent_code, path = heapq.heappop(open_set) + + if current_code in closed_set: + continue + + closed_set.add(current_code) + current_path = path + [current_code] + + if current_code == end_code: + # 閲嶅缓璺緞 + return self._build_path_result(current_path) + + # 浣跨敤棰勮绠楃殑閭绘帴琛� + for neighbor_code, direction in self.adjacency[current_code]: + if neighbor_code in closed_set: + continue + + # 绾︽潫妫�鏌� + if constraint_check and constraint_check(neighbor_code): + continue + + # 浠d环璁$畻 + g_cost = g_scores[current_code] + 1 + + if neighbor_code not in g_scores or g_cost < g_scores[neighbor_code]: + g_scores[neighbor_code] = g_cost + + # 鍚彂寮忚绠� + neighbor_coord = (self.path_mapping[neighbor_code]['x'], + self.path_mapping[neighbor_code]['y']) + h_cost = abs(neighbor_coord[0] - end_coord[0]) + abs(neighbor_coord[1] - end_coord[1]) + f_cost = g_cost + h_cost + + heapq.heappush(open_set, (f_cost, neighbor_code, current_code, current_path)) + + return None + + def _build_constraint_checker(self, constraints: List[Tuple[int, int, float]]): + """鏋勫缓绾︽潫妫�鏌ュ嚱鏁�""" + def check_constraints(code: str) -> bool: + coord = (self.path_mapping[code]['x'], self.path_mapping[code]['y']) + for cx, cy, radius in constraints: + if (coord[0] - cx) ** 2 + (coord[1] - cy) ** 2 <= radius ** 2: + return True + return False + return check_constraints + + def _build_path_result(self, path_codes: List[str]) -> PlannedPath: + """鏋勫缓璺緞缁撴灉""" + result_codes = [] + + for i, code in enumerate(path_codes): + direction = "90" # 榛樿鏂瑰悜 + + if i < len(path_codes) - 1: + # 浣跨敤棰勮绠楃殑閭绘帴琛ㄦ煡鎵炬柟鍚� + next_code = path_codes[i + 1] + for neighbor_code, neighbor_direction in self.adjacency[code]: + if neighbor_code == next_code: + direction = neighbor_direction + break + + result_codes.append(PathCode(code=code, direction=direction)) + + return PlannedPath(agvId="", codeList=result_codes) + + +# 纰版挒妫�娴嬪櫒 +class FastCollisionDetector: + """纰版挒妫�娴嬪櫒""" + + def __init__(self, min_distance: float = 3.0): + self.min_distance = min_distance + self.logger = logging.getLogger(__name__) + + def detect_conflicts(self, planned_paths: List[Dict]) -> List[Dict]: + """ + 纰版挒妫�娴� - 鍩轰簬鏃堕棿姝ョ殑浼樺寲妫�娴� + + Args: + planned_paths: 瑙勫垝璺緞鍒楄〃 + + Returns: + List[Dict]: 鍐茬獊鍒楄〃 + """ + conflicts = [] + if len(planned_paths) < 2: + return conflicts + + # 鏋勫缓鏃堕棿-浣嶇疆鏄犲皠 + time_positions = defaultdict(list) + + for path_data in planned_paths: + agv_id = path_data['agvId'] + code_list = path_data.get('codeList', []) + + for time_step, path_code in enumerate(code_list): + # 蹇�熸彁鍙栦綅缃爜 + if isinstance(path_code, dict): + position = path_code.get('code', '') + elif hasattr(path_code, 'code'): + position = path_code.code + else: + position = str(path_code) + + if position: + time_positions[time_step].append((agv_id, position)) + + # 蹇�熷啿绐佹娴� + for time_step, agv_positions in time_positions.items(): + if len(agv_positions) < 2: + continue + + # 浣嶇疆鍒嗙粍妫�鏌� + position_groups = defaultdict(list) + for agv_id, position in agv_positions: + position_groups[position].append(agv_id) + + # 鍙戠幇鍐茬獊 + for position, agv_list in position_groups.items(): + if len(agv_list) > 1: + conflicts.append({ + 'type': 'position_conflict', + 'time_step': time_step, + 'position': position, + 'agv_ids': agv_list, + 'severity': 'high' + }) + + self.logger.debug(f"蹇�熸娴嬪埌 {len(conflicts)} 涓啿绐�") + return conflicts + + +# 纰版挒瑙e喅鍣� +class FastCollisionResolver: + """纰版挒瑙e喅鍣�""" + + def __init__(self, path_mapping: Dict[str, Dict[str, int]], + collision_detector: FastCollisionDetector, agv_manager=None): + self.path_mapping = path_mapping + self.collision_detector = collision_detector + self.agv_manager = agv_manager + self.logger = logging.getLogger(__name__) + + def resolve_conflicts(self, planned_paths: List[Dict], conflicts: List[Dict], executing_tasks: List[Dict] = None) -> List[Dict]: + """ + 瑙e喅鍐茬獊 + + Args: + planned_paths: 瑙勫垝璺緞鍒楄〃 + conflicts: 鍐茬獊鍒楄〃 + executing_tasks: 鎵ц浠诲姟鍒楄〃 + + Returns: + List[Dict]: 瑙e喅鍐茬獊鍚庣殑璺緞鍒楄〃 + """ + if not conflicts: + return planned_paths + + # 鏋勫缓璺緞瀛楀吀渚夸簬蹇�熻闂� + paths_dict = {path['agvId']: path for path in planned_paths} + + # 鎸夋椂闂存鎺掑簭澶勭悊鍐茬獊 + sorted_conflicts = sorted(conflicts, key=lambda x: x.get('time_step', 0)) + + for conflict in sorted_conflicts: + if conflict['type'] == 'position_conflict': + agv_ids = conflict['agv_ids'] + time_step = conflict['time_step'] + + # 绠�鍗曠瓥鐣ワ細淇濈暀绗竴涓狝GV锛屽叾浠栧欢杩� + sorted_agv_ids = sorted(agv_ids) + + for i, agv_id in enumerate(sorted_agv_ids[1:], 1): + if agv_id in paths_dict: + self._add_delay_to_path(paths_dict[agv_id], delay_steps=i) + self.logger.debug(f"涓篈GV {agv_id} 娣诲姞 {i} 姝ュ欢杩�") + + return list(paths_dict.values()) + + def _add_delay_to_path(self, path_data: Dict, delay_steps: int): + """涓鸿矾寰勬坊鍔犲欢杩熸楠�""" + if delay_steps <= 0 or not path_data.get('codeList'): + return + + # 鍦ㄨ矾寰勫紑濮嬪閲嶅绗竴涓綅缃� + first_code = path_data['codeList'][0] + + # 鍒涘缓寤惰繜浠g爜 + if isinstance(first_code, dict): + delay_code = first_code.copy() + else: + delay_code = { + 'code': first_code.code if hasattr(first_code, 'code') else str(first_code), + 'direction': first_code.direction if hasattr(first_code, 'direction') else "90" + } + + # 娣诲姞寤惰繜姝ラ + for _ in range(delay_steps): + path_data['codeList'].insert(0, delay_code) + + def validate_four_direction_movement(self, planned_paths: List[Dict]) -> List[Dict]: + """楠岃瘉鍥涘悜绉诲姩绾︽潫""" + return planned_paths + + +class PathPlanningFactory: + """璺緞瑙勫垝鍣ㄥ伐鍘傜被""" + + @staticmethod + def create_path_planner(algorithm_type: str, path_mapping: Dict[str, Dict[str, int]]) -> PathPlanner: + """ + 鍒涘缓璺緞瑙勫垝鍣ㄥ疄渚� + + Args: + algorithm_type: 绠楁硶绫诲瀷 + path_mapping: 璺緞鐐规槧灏勫瓧鍏� + + Returns: + PathPlanner: 璺緞瑙勫垝鍣ㄥ疄渚� + """ + algorithm_upper = algorithm_type.upper() + + if algorithm_upper in ["A_STAR", "ASTAR", "A*", "DIJKSTRA", "GREEDY"]: + return AStarPathPlanner(path_mapping) + else: + # 榛樿A*绠楁硶 + logging.getLogger(__name__).warning(f"鏈煡绠楁硶绫诲瀷 {algorithm_type}锛屼娇鐢ㄩ粯璁*绠楁硶") + return AStarPathPlanner(path_mapping) + + @staticmethod + def create_batch_path_planner(algorithm_type: str, path_mapping: Dict[str, Dict[str, int]], + agv_manager=None) -> BatchPathPlanner: + """ + 鍒涘缓鎵归噺璺緞瑙勫垝鍣ㄥ疄渚� + + Args: + algorithm_type: 鍩虹绠楁硶绫诲瀷 + path_mapping: 璺緞鐐规槧灏勫瓧鍏� + agv_manager: AGV绠$悊鍣紝鐢ㄤ簬鑾峰彇AGV鐘舵�佷俊鎭� + + Returns: + BatchPathPlanner: 鎵归噺璺緞瑙勫垝鍣ㄥ疄渚� + """ + return BatchPathPlanner(path_mapping, algorithm_type, agv_manager) + +# 瀵煎嚭鏍稿績绫诲拰鍑芥暟 +__all__ = [ + 'ExecutingTaskExtractor', + 'BatchPathPlanner', + 'PathPlanner', + 'AStarPathPlanner', + 'PathPlanningFactory', + 'FastCollisionDetector', + 'FastCollisionResolver' +] \ No newline at end of file diff --git a/algorithm_system/algorithms/task_allocation.py b/algorithm_system/algorithms/task_allocation.py new file mode 100644 index 0000000..883ed50 --- /dev/null +++ b/algorithm_system/algorithms/task_allocation.py @@ -0,0 +1,687 @@ +""" +浠诲姟鍒嗛厤绠楁硶 +""" +import time +import random +import logging +from typing import Dict, List, Tuple, Optional, Set, Any +from collections import defaultdict +from abc import ABC, abstractmethod + +from common.data_models import TaskData, AGVStatus, TaskAssignment, BackpackData +from algorithm_system.models.agv_model import AGVModel, AGVModelManager +from common.utils import get_coordinate_from_path_id, calculate_distance, calculate_manhattan_distance +from dataclasses import dataclass + + +class TaskAllocation(ABC): + """浠诲姟鍒嗛厤绠楁硶鍩虹被""" + + def __init__(self, agv_manager: AGVModelManager): + """ + 鍒濆鍖栦换鍔″垎閰嶇畻娉� + + Args: + agv_manager: AGV妯″瀷绠$悊鍣� + """ + self.agv_manager = agv_manager + self.logger = logging.getLogger(__name__) + + @abstractmethod + def allocate_tasks(self, tasks: List[TaskData]) -> List[TaskAssignment]: + """ + 鍒嗛厤浠诲姟缁橝GV + + Args: + tasks: 寰呭垎閰嶇殑浠诲姟鍒楄〃 + + Returns: + List[TaskAssignment]: 鍒嗛厤缁撴灉鍒楄〃 + """ + pass + + def find_available_backpack_slot(self, agv_status: AGVStatus) -> Optional[int]: + """ + 鏌ユ壘AGV鐨勫彲鐢ㄨ儗绡撲綅缃� + + Args: + agv_status: AGV鐘舵�佷俊鎭� + + Returns: + Optional[int]: 鍙敤鐨勮儗绡撲綅缃紪鍙凤紝濡傛灉娌℃湁鍙敤浣嶇疆鍒欒繑鍥濶one + """ + if not agv_status.backpack: + # 濡傛灉娌℃湁鑳岀瘬淇℃伅锛屽亣璁句粠绗竴涓綅缃紑濮� + self.logger.warning(f"AGV {agv_status.agvId} 娌℃湁鑳岀瘬淇℃伅锛屽垎閰嶅埌浣嶇疆0") + return 0 + + # 鏌ユ壘绌洪棽涓旀湭鎵ц浠诲姟鐨勮儗绡撲綅缃� + for backpack_item in agv_status.backpack: + if not backpack_item.loaded and not backpack_item.execute and not backpack_item.taskId: + self.logger.debug(f"AGV {agv_status.agvId} 鎵惧埌鍙敤鑳岀瘬浣嶇疆: {backpack_item.index}") + return backpack_item.index + + # 濡傛灉鎵�鏈変綅缃兘琚崰鐢紝杩斿洖None + self.logger.debug(f"AGV {agv_status.agvId} 娌℃湁鍙敤鐨勮儗绡撲綅缃�") + return None + + def get_agv_available_capacity(self, agv_status: AGVStatus) -> int: + """ + 鑾峰彇AGV鐨勫彲鐢ㄨ儗绡撳閲� + + Args: + agv_status: AGV鐘舵�佷俊鎭� + + Returns: + int: 鍙敤鑳岀瘬鏁伴噺 + """ + if not agv_status.backpack: + return 1 # 鍋囪鑷冲皯鏈変竴涓儗绡撲綅缃� + + available_count = 0 + for backpack_item in agv_status.backpack: + if not backpack_item.loaded and not backpack_item.execute and not backpack_item.taskId: + available_count += 1 + + return available_count + + def assign_task_with_backpack(self, agv_model, task: TaskData, lev_id: int) -> bool: + """ + 灏嗕换鍔″垎閰嶇粰AGV鐨勬寚瀹氳儗绡撲綅缃� + + Args: + agv_model: AGV妯″瀷 + task: 浠诲姟鏁版嵁 + lev_id: 鑳岀瘬浣嶇疆缂栧彿 + + Returns: + bool: 鍒嗛厤鏄惁鎴愬姛 + """ + try: + # 浣跨敤AGV妯″瀷鐨刟ssign_task鏂规硶 + success = agv_model.assign_task( + task_id=task.taskId, + priority=task.priority, + start_code=task.start, + end_code=task.end + ) + + if success: + self.logger.info(f"浠诲姟 {task.taskId} 鎴愬姛鍒嗛厤缁橝GV {agv_model.agvId} 鐨勮儗绡撲綅缃� {lev_id}") + return True + else: + self.logger.warning(f"浠诲姟 {task.taskId} 鍒嗛厤缁橝GV {agv_model.agvId} 澶辫触") + return False + + except Exception as e: + self.logger.error(f"鍒嗛厤浠诲姟鏃跺彂鐢熷紓甯�: {e}") + return False + + +class NearestFirstAllocation(TaskAllocation): + """鏈�杩戜紭鍏堝垎閰嶇畻娉�""" + + def allocate_tasks(self, tasks: List[TaskData]) -> List[TaskAssignment]: + """ + 浣跨敤鏈�杩戜紭鍏堢瓥鐣ュ垎閰嶄换鍔� + + Args: + tasks: 寰呭垎閰嶇殑浠诲姟鍒楄〃 + + Returns: + List[TaskAssignment]: 鍒嗛厤缁撴灉鍒楄〃 + """ + if not tasks: + return [] + + # 鑾峰彇鍙敤鐨凙GV + available_agvs = self.agv_manager.get_available_agvs() + + if not available_agvs: + self.logger.warning("娌℃湁鍙敤鐨凙GV杩涜浠诲姟鍒嗛厤") + return [] + + # 1. 棣栧厛妫�鏌ヤ换鍔℃槸鍚﹀凡缁忓垎閰嶏紝閬垮厤閲嶅鍒嗛厤 + already_assigned_tasks = set() + all_agvs = self.agv_manager.get_all_agvs() + for agv in all_agvs: + if agv.backpack: + for backpack_item in agv.backpack: + if backpack_item.taskId: + already_assigned_tasks.add(backpack_item.taskId) + self.logger.info(f"浠诲姟 {backpack_item.taskId} 宸插垎閰嶇粰 AGV {agv.agvId}锛岃烦杩囬噸澶嶅垎閰�") + + # 2. 杩囨护鎺夊凡鍒嗛厤鐨勪换鍔� + unassigned_tasks = [task for task in tasks if task.taskId not in already_assigned_tasks] + + if not unassigned_tasks: + self.logger.info("鎵�鏈変换鍔¢兘宸插垎閰嶏紝鏃犻渶閲嶆柊鍒嗛厤") + return [] + + self.logger.info(f"鎬讳换鍔℃暟: {len(tasks)}, 宸插垎閰�: {len(already_assigned_tasks)}, 寰呭垎閰�: {len(unassigned_tasks)}") + + assignments = [] + path_mapping = self.agv_manager.path_mapping + + # 瀵规瘡涓换鍔℃壘鍒版渶杩戠殑AGV + for task in unassigned_tasks: + if not available_agvs: + break + + # 鑾峰彇浠诲姟璧风偣鍧愭爣 + task_start_coord = get_coordinate_from_path_id(task.start, path_mapping) + if not task_start_coord: + self.logger.warning(f"鏃犳硶鑾峰彇浠诲姟 {task.taskId} 璧风偣 {task.start} 鐨勫潗鏍�") + continue + + # 鎵惧埌璺濈鏈�杩戠殑AGV + nearest_agv = None + min_distance = float('inf') + + for agv in available_agvs: + if agv.coordinates: + distance = calculate_manhattan_distance(agv.coordinates, task_start_coord) + + # 濡傛灉AGV宸叉湁浠诲姟锛岃绠楀畬鎴愬綋鍓嶄换鍔″悗鍒版柊浠诲姟璧风偣鐨勮窛绂� + if agv.current_task_count > 0: + # 绠�鍖栵細鍋囪AGV闇�瑕侀澶栨椂闂村畬鎴愬綋鍓嶄换鍔� + distance += agv.current_task_count * 10 + + if distance < min_distance: + min_distance = distance + nearest_agv = agv + + if nearest_agv and nearest_agv.can_accept_task(task.priority): + # 鑾峰彇AGV鐨勫師濮嬬姸鎬佹暟鎹潵鏌ユ壘鍙敤鑳岀瘬浣嶇疆 + agv_status = None + for agv_state in self.agv_manager.get_all_agv_status(): + if agv_state.agvId == nearest_agv.agvId: + agv_status = agv_state + break + + if agv_status: + # 鏌ユ壘鍙敤鐨勮儗绡撲綅缃� + available_lev_id = self.find_available_backpack_slot(agv_status) + + if available_lev_id is not None: + # 鍒嗛厤浠诲姟鍒版寚瀹氳儗绡撲綅缃� + success = self.assign_task_with_backpack(nearest_agv, task, available_lev_id) + if success: + assignments.append(TaskAssignment( + taskId=task.taskId, + agvId=nearest_agv.agvId, + lev_id=available_lev_id + )) + + self.logger.info(f"浠诲姟 {task.taskId} 鍒嗛厤缁欐渶杩戠殑AGV {nearest_agv.agvId}锛岃儗绡撲綅缃�: {available_lev_id}锛岃窛绂�: {min_distance}") + + # 妫�鏌GV鏄惁杩樻湁鍙敤鑳岀瘬浣嶇疆 + remaining_capacity = self.get_agv_available_capacity(agv_status) - 1 + if remaining_capacity <= 0: + available_agvs.remove(nearest_agv) + else: + self.logger.warning(f"浠诲姟 {task.taskId} 鍒嗛厤缁橝GV {nearest_agv.agvId} 澶辫触") + else: + self.logger.warning(f"AGV {nearest_agv.agvId} 娌℃湁鍙敤鐨勮儗绡撲綅缃�") + available_agvs.remove(nearest_agv) + else: + self.logger.warning(f"鏃犳硶鑾峰彇AGV {nearest_agv.agvId} 鐨勭姸鎬佷俊鎭�") + + return assignments + + +class LoadBalancedAllocation(TaskAllocation): + """璐熻浇鍧囪 鍒嗛厤绠楁硶""" + + def allocate_tasks(self, tasks: List[TaskData]) -> List[TaskAssignment]: + """ + 浣跨敤璐熻浇鍧囪 绛栫暐鍒嗛厤浠诲姟 + + Args: + tasks: 寰呭垎閰嶇殑浠诲姟鍒楄〃 + + Returns: + List[TaskAssignment]: 鍒嗛厤缁撴灉鍒楄〃 + """ + if not tasks: + return [] + + # 鑾峰彇鎵�鏈堿GV + all_agvs = self.agv_manager.get_all_agvs() + + if not all_agvs: + self.logger.warning("娌℃湁AGV杩涜浠诲姟鍒嗛厤") + return [] + + # 1. 棣栧厛妫�鏌ヤ换鍔℃槸鍚﹀凡缁忓垎閰嶏紝閬垮厤閲嶅鍒嗛厤 + already_assigned_tasks = set() + for agv in all_agvs: + if agv.backpack: + for backpack_item in agv.backpack: + if backpack_item.taskId: + already_assigned_tasks.add(backpack_item.taskId) + self.logger.info(f"浠诲姟 {backpack_item.taskId} 宸插垎閰嶇粰 AGV {agv.agvId}锛岃烦杩囬噸澶嶅垎閰�") + + # 2. 杩囨护鎺夊凡鍒嗛厤鐨勪换鍔� + unassigned_tasks = [task for task in tasks if task.taskId not in already_assigned_tasks] + + if not unassigned_tasks: + self.logger.info("鎵�鏈変换鍔¢兘宸插垎閰嶏紝鏃犻渶閲嶆柊鍒嗛厤") + return [] + + self.logger.info(f"鎬讳换鍔℃暟: {len(tasks)}, 宸插垎閰�: {len(already_assigned_tasks)}, 寰呭垎閰�: {len(unassigned_tasks)}") + + assignments = [] + path_mapping = self.agv_manager.path_mapping + + # 鎸変紭鍏堢骇鎺掑簭浠诲姟 + sorted_tasks = sorted(unassigned_tasks, key=lambda t: t.priority, reverse=True) + + # 瀵规瘡涓换鍔″垎閰嶇粰璐熻浇鏈�浣庣殑AGV + for task in sorted_tasks: + # 鑾峰彇浠诲姟璧风偣鍧愭爣 + task_start_coord = get_coordinate_from_path_id(task.start, path_mapping) + if not task_start_coord: + self.logger.warning(f"鏃犳硶鑾峰彇浠诲姟 {task.taskId} 璧风偣 {task.start} 鐨勫潗鏍�") + continue + + # 鎸夎礋杞藉拰璺濈鎺掑簭AGV + agv_scores = [] + for agv in all_agvs: + if not agv.can_accept_task(task.priority): + continue + + # 璁$畻璐熻浇寰楀垎锛堣礋杞借秺浣庡緱鍒嗚秺楂橈級 + load_score = 1.0 - agv.get_workload_ratio() + + # 璁$畻璺濈寰楀垎锛堣窛绂昏秺杩戝緱鍒嗚秺楂橈級 + distance_score = 0.0 + if agv.coordinates and task_start_coord: + distance = calculate_manhattan_distance(agv.coordinates, task_start_coord) + distance_score = 1.0 / (1.0 + distance / 100.0) # 褰掍竴鍖栬窛绂诲緱鍒� + + # 璁$畻鏁堢巼寰楀垎 + efficiency_score = agv.calculate_efficiency_score(path_mapping) + + # 缁煎悎寰楀垎 + total_score = 0.4 * load_score + 0.3 * distance_score + 0.3 * efficiency_score + agv_scores.append((agv, total_score)) + + if not agv_scores: + # 璇︾粏鍒嗘瀽涓轰粈涔堟病鏈堿GV鍙互鎺ュ彈浠诲姟 + total_agvs = len(all_agvs) + busy_count = 0 + low_battery_count = 0 + overloaded_count = 0 + status_invalid_count = 0 + + for agv in all_agvs: + if agv.is_overloaded(): + overloaded_count += 1 + elif str(agv.status) not in ["0", "1", "2"]: + status_invalid_count += 1 + elif agv.need_charging(): + low_battery_count += 1 + else: + busy_count += 1 + + self.logger.warning(f"娌℃湁AGV鍙互鎺ュ彈浠诲姟 {task.taskId} - 璇︾粏鍒嗘瀽:") + self.logger.warning(f" 鎬籄GV鏁�: {total_agvs}") + self.logger.warning(f" 浠诲姟婊¤浇: {overloaded_count}") + self.logger.warning(f" 鐘舵�佸紓甯�: {status_invalid_count}") + self.logger.warning(f" 鐢甸噺杩囦綆: {low_battery_count}") + self.logger.warning(f" 鍏朵粬鍘熷洜: {busy_count}") + self.logger.warning(f" 浠诲姟浼樺厛绾�: {task.priority}") + continue + + # 閫夋嫨寰楀垎鏈�楂樼殑AGV + agv_scores.sort(key=lambda x: x[1], reverse=True) + best_agv = agv_scores[0][0] + + # 鑾峰彇AGV鐨勫師濮嬬姸鎬佹暟鎹潵鏌ユ壘鍙敤鑳岀瘬浣嶇疆 + agv_status = self.agv_manager.get_agv_status(best_agv.agvId) + + if agv_status: + # 鏌ユ壘鍙敤鐨勮儗绡撲綅缃� + available_lev_id = self.find_available_backpack_slot(agv_status) + + if available_lev_id is not None: + # 鍒嗛厤浠诲姟鍒版寚瀹氳儗绡撲綅缃� + success = self.assign_task_with_backpack(best_agv, task, available_lev_id) + if success: + assignments.append(TaskAssignment( + taskId=task.taskId, + agvId=best_agv.agvId, + lev_id=available_lev_id + )) + + self.logger.info(f"浠诲姟 {task.taskId} 鍒嗛厤缁欒礋杞藉潎琛$殑AGV {best_agv.agvId}锛岃儗绡撲綅缃�: {available_lev_id}锛屽緱鍒�: {agv_scores[0][1]:.3f}") + else: + self.logger.warning(f"浠诲姟 {task.taskId} 鍒嗛厤缁橝GV {best_agv.agvId} 澶辫触") + else: + self.logger.warning(f"AGV {best_agv.agvId} 娌℃湁鍙敤鐨勮儗绡撲綅缃�") + else: + self.logger.warning(f"鏃犳硶鑾峰彇AGV {best_agv.agvId} 鐨勭姸鎬佷俊鎭�") + + return assignments + + +class PriorityFirstAllocation(TaskAllocation): + """浼樺厛绾т紭鍏堝垎閰嶇畻娉�""" + + def allocate_tasks(self, tasks: List[TaskData]) -> List[TaskAssignment]: + """ + 浣跨敤浼樺厛绾т紭鍏堢瓥鐣ュ垎閰嶄换鍔� + + Args: + tasks: 寰呭垎閰嶇殑浠诲姟鍒楄〃 + + Returns: + List[TaskAssignment]: 鍒嗛厤缁撴灉鍒楄〃 + """ + if not tasks: + return [] + + # 鑾峰彇鍙敤鐨凙GV + available_agvs = self.agv_manager.get_available_agvs() + + if not available_agvs: + self.logger.warning("娌℃湁鍙敤鐨凙GV杩涜浠诲姟鍒嗛厤") + return [] + + # 1. 棣栧厛妫�鏌ヤ换鍔℃槸鍚﹀凡缁忓垎閰嶏紝閬垮厤閲嶅鍒嗛厤 + already_assigned_tasks = set() + all_agvs = self.agv_manager.get_all_agvs() + for agv in all_agvs: + if agv.backpack: + for backpack_item in agv.backpack: + if backpack_item.taskId: + already_assigned_tasks.add(backpack_item.taskId) + self.logger.info(f"浠诲姟 {backpack_item.taskId} 宸插垎閰嶇粰 AGV {agv.agvId}锛岃烦杩囬噸澶嶅垎閰�") + + # 2. 杩囨护鎺夊凡鍒嗛厤鐨勪换鍔� + unassigned_tasks = [task for task in tasks if task.taskId not in already_assigned_tasks] + + if not unassigned_tasks: + self.logger.info("鎵�鏈変换鍔¢兘宸插垎閰嶏紝鏃犻渶閲嶆柊鍒嗛厤") + return [] + + self.logger.info(f"鎬讳换鍔℃暟: {len(tasks)}, 宸插垎閰�: {len(already_assigned_tasks)}, 寰呭垎閰�: {len(unassigned_tasks)}") + + # 鎸変紭鍏堢骇鎺掑簭浠诲姟锛堥珮浼樺厛绾у湪鍓嶏級 + sorted_tasks = sorted(unassigned_tasks, key=lambda t: t.priority, reverse=True) + + assignments = [] + path_mapping = self.agv_manager.path_mapping + + # 浼樺厛鍒嗛厤楂樹紭鍏堢骇浠诲姟 + for task in sorted_tasks: + if not available_agvs: + break + + # 鑾峰彇浠诲姟璧风偣鍧愭爣 + task_start_coord = get_coordinate_from_path_id(task.start, path_mapping) + if not task_start_coord: + self.logger.warning(f"鏃犳硶鑾峰彇浠诲姟 {task.taskId} 璧风偣 {task.start} 鐨勫潗鏍�") + continue + + # 涓洪珮浼樺厛绾т换鍔¢�夋嫨鏈�浣矨GV + best_agv = None + best_score = -1 + + for agv in available_agvs: + if not agv.can_accept_task(task.priority): + continue + + # 璁$畻缁煎悎寰楀垎 + distance_score = 0.0 + if agv.coordinates and task_start_coord: + distance = calculate_manhattan_distance(agv.coordinates, task_start_coord) + distance_score = 1.0 / (1.0 + distance / 50.0) + + efficiency_score = agv.calculate_efficiency_score(path_mapping) + capacity_score = agv.get_task_capacity() / agv.max_capacity + + # 楂樹紭鍏堢骇浠诲姟鏇存敞閲嶆晥鐜囧拰璺濈 + total_score = 0.5 * distance_score + 0.3 * efficiency_score + 0.2 * capacity_score + + if total_score > best_score: + best_score = total_score + best_agv = agv + + if best_agv: + # 鑾峰彇AGV鐨勫師濮嬬姸鎬佹暟鎹潵鏌ユ壘鍙敤鑳岀瘬浣嶇疆 + agv_status = self.agv_manager.get_agv_status(best_agv.agvId) + + if agv_status: + # 鏌ユ壘鍙敤鐨勮儗绡撲綅缃� + available_lev_id = self.find_available_backpack_slot(agv_status) + + if available_lev_id is not None: + # 鍒嗛厤浠诲姟鍒版寚瀹氳儗绡撲綅缃� + success = self.assign_task_with_backpack(best_agv, task, available_lev_id) + if success: + assignments.append(TaskAssignment( + taskId=task.taskId, + agvId=best_agv.agvId, + lev_id=available_lev_id + )) + + self.logger.info(f"楂樹紭鍏堢骇浠诲姟 {task.taskId} (浼樺厛绾�: {task.priority}) 鍒嗛厤缁橝GV {best_agv.agvId}锛岃儗绡撲綅缃�: {available_lev_id}") + + # 妫�鏌GV鏄惁杩樻湁鍙敤鑳岀瘬浣嶇疆 + remaining_capacity = self.get_agv_available_capacity(agv_status) - 1 + if remaining_capacity <= 0: + available_agvs.remove(best_agv) + else: + self.logger.warning(f"浠诲姟 {task.taskId} 鍒嗛厤缁橝GV {best_agv.agvId} 澶辫触") + else: + self.logger.warning(f"AGV {best_agv.agvId} 娌℃湁鍙敤鐨勮儗绡撲綅缃�") + available_agvs.remove(best_agv) + else: + self.logger.warning(f"鏃犳硶鑾峰彇AGV {best_agv.agvId} 鐨勭姸鎬佷俊鎭�") + + return assignments + + +class MultiObjectiveAllocation(TaskAllocation): + """澶氱洰鏍囦紭鍖栧垎閰嶇畻娉�""" + + def __init__(self, agv_manager: AGVModelManager, + distance_weight: float = 0.4, + load_weight: float = 0.3, + efficiency_weight: float = 0.3): + """ + 鍒濆鍖栧鐩爣浼樺寲鍒嗛厤绠楁硶 + + Args: + agv_manager: AGV妯″瀷绠$悊鍣� + distance_weight: 璺濈鏉冮噸 + load_weight: 璐熻浇鏉冮噸 + efficiency_weight: 鏁堢巼鏉冮噸 + """ + super().__init__(agv_manager) + self.distance_weight = distance_weight + self.load_weight = load_weight + self.efficiency_weight = efficiency_weight + + def allocate_tasks(self, tasks: List[TaskData]) -> List[TaskAssignment]: + """ + 浣跨敤澶氱洰鏍囦紭鍖栫瓥鐣ュ垎閰嶄换鍔� + + Args: + tasks: 寰呭垎閰嶇殑浠诲姟鍒楄〃 + + Returns: + List[TaskAssignment]: 鍒嗛厤缁撴灉鍒楄〃 + """ + if not tasks: + return [] + + # 鑾峰彇鎵�鏈堿GV + all_agvs = self.agv_manager.get_all_agvs() + + if not all_agvs: + self.logger.warning("娌℃湁AGV杩涜浠诲姟鍒嗛厤") + return [] + + # 1. 棣栧厛妫�鏌ヤ换鍔℃槸鍚﹀凡缁忓垎閰嶏紝閬垮厤閲嶅鍒嗛厤 + already_assigned_tasks = set() + for agv in all_agvs: + if agv.backpack: + for backpack_item in agv.backpack: + if backpack_item.taskId: + already_assigned_tasks.add(backpack_item.taskId) + self.logger.info(f"浠诲姟 {backpack_item.taskId} 宸插垎閰嶇粰 AGV {agv.agvId}锛岃烦杩囬噸澶嶅垎閰�") + + # 2. 杩囨护鎺夊凡鍒嗛厤鐨勪换鍔� + unassigned_tasks = [task for task in tasks if task.taskId not in already_assigned_tasks] + + if not unassigned_tasks: + self.logger.info("鎵�鏈変换鍔¢兘宸插垎閰嶏紝鏃犻渶閲嶆柊鍒嗛厤") + return [] + + self.logger.info(f"鎬讳换鍔℃暟: {len(tasks)}, 宸插垎閰�: {len(already_assigned_tasks)}, 寰呭垎閰�: {len(unassigned_tasks)}") + + assignments = [] + path_mapping = self.agv_manager.path_mapping + + # 瀵规瘡涓换鍔�-AGV瀵硅绠楀緱鍒� + task_agv_scores = {} + + for task in unassigned_tasks: + task_start_coord = get_coordinate_from_path_id(task.start, path_mapping) + if not task_start_coord: + continue + + for agv in all_agvs: + if not agv.can_accept_task(task.priority): + continue + + # 璺濈寰楀垎 + distance_score = 0.0 + if agv.coordinates: + distance = calculate_manhattan_distance(agv.coordinates, task_start_coord) + distance_score = 1.0 / (1.0 + distance / 100.0) + + # 璐熻浇寰楀垎 + load_score = 1.0 - agv.get_workload_ratio() + + # 鏁堢巼寰楀垎 + efficiency_score = agv.calculate_efficiency_score(path_mapping) + + # 璁$畻缁煎悎寰楀垎 + total_score = ( + self.distance_weight * distance_score + + self.load_weight * load_score + + self.efficiency_weight * efficiency_score + ) + + task_agv_scores[(task.taskId, agv.agvId)] = total_score + + # 浣跨敤璐績绠楁硶杩涜鍖归厤 + assignments = self._greedy_matching(unassigned_tasks, all_agvs, task_agv_scores) + + return assignments + + def _greedy_matching(self, tasks: List[TaskData], agvs: List[AGVModel], + scores: Dict[Tuple[str, str], float]) -> List[TaskAssignment]: + """ + 浣跨敤璐績绠楁硶杩涜浠诲姟-AGV鍖归厤 + + Args: + tasks: 浠诲姟鍒楄〃 + agvs: AGV鍒楄〃 + scores: 浠诲姟-AGV瀵圭殑寰楀垎 + + Returns: + List[TaskAssignment]: 鍒嗛厤缁撴灉 + """ + assignments = [] + remaining_tasks = [task.taskId for task in tasks] + + # 閲嶅鍒嗛厤鐩村埌娌℃湁浠诲姟鎴栨病鏈夊彲鐢ˋGV + while remaining_tasks: + # 鎵惧埌寰楀垎鏈�楂樼殑浠诲姟-AGV瀵� + best_score = -1 + best_task_id = None + best_agv = None + + for task_id in remaining_tasks: + for agv in agvs: + if agv.is_overloaded(): + continue + + score = scores.get((task_id, agv.agvId), 0.0) + if score > best_score: + best_score = score + best_task_id = task_id + best_agv = agv + + if best_task_id and best_agv: + # 鑾峰彇AGV鐨勫師濮嬬姸鎬佹暟鎹潵鏌ユ壘鍙敤鑳岀瘬浣嶇疆 + agv_status = self.agv_manager.get_agv_status(best_agv.agvId) + + if agv_status: + # 鏌ユ壘鍙敤鐨勮儗绡撲綅缃� + available_lev_id = self.find_available_backpack_slot(agv_status) + + if available_lev_id is not None: + # 鎵惧埌瀵瑰簲鐨勪换鍔″璞� + task = next((t for t in tasks if t.taskId == best_task_id), None) + if task: + # 鍒嗛厤浠诲姟鍒版寚瀹氳儗绡撲綅缃� + success = self.assign_task_with_backpack(best_agv, task, available_lev_id) + if success: + assignments.append(TaskAssignment( + taskId=best_task_id, + agvId=best_agv.agvId, + lev_id=available_lev_id + )) + + remaining_tasks.remove(best_task_id) + self.logger.info(f"澶氱洰鏍囦紭鍖栵細浠诲姟 {best_task_id} 鍒嗛厤缁橝GV {best_agv.agvId}锛岃儗绡撲綅缃�: {available_lev_id}锛屽緱鍒�: {best_score:.3f}") + else: + self.logger.warning(f"浠诲姟 {best_task_id} 鍒嗛厤缁橝GV {best_agv.agvId} 澶辫触") + break + else: + self.logger.error(f"鎵句笉鍒颁换鍔� {best_task_id} 鐨勮缁嗕俊鎭�") + break + else: + self.logger.debug(f"AGV {best_agv.agvId} 娌℃湁鍙敤鐨勮儗绡撲綅缃紝璺宠繃") + break + else: + self.logger.warning(f"鏃犳硶鑾峰彇AGV {best_agv.agvId} 鐨勭姸鎬佷俊鎭�") + break + else: + break + + return assignments + + +class TaskAllocationFactory: + """浠诲姟鍒嗛厤绠楁硶宸ュ巶绫�""" + + @staticmethod + def create_allocator(algorithm_type: str, agv_manager: AGVModelManager) -> TaskAllocation: + """ + 鍒涘缓浠诲姟鍒嗛厤绠楁硶 + + Args: + algorithm_type: 绠楁硶绫诲瀷 + agv_manager: AGV妯″瀷绠$悊鍣� + + Returns: + TaskAllocation: 浠诲姟鍒嗛厤绠楁硶瀵硅薄 + """ + if algorithm_type == "NEAREST_FIRST": + return NearestFirstAllocation(agv_manager) + elif algorithm_type == "LOAD_BALANCED": + return LoadBalancedAllocation(agv_manager) + elif algorithm_type == "PRIORITY_FIRST": + return PriorityFirstAllocation(agv_manager) + elif algorithm_type == "MULTI_OBJECTIVE": + return MultiObjectiveAllocation(agv_manager) + else: + # 榛樿浣跨敤璐熻浇鍧囪 绠楁硶 + return LoadBalancedAllocation(agv_manager) \ No newline at end of file diff --git a/algorithm_system/models/__init__.py b/algorithm_system/models/__init__.py new file mode 100644 index 0000000..d63e257 --- /dev/null +++ b/algorithm_system/models/__init__.py @@ -0,0 +1,2 @@ +# Algorithm System Models Module +__version__ = "1.0.0" \ No newline at end of file diff --git a/algorithm_system/models/agv_model.py b/algorithm_system/models/agv_model.py new file mode 100644 index 0000000..6131862 --- /dev/null +++ b/algorithm_system/models/agv_model.py @@ -0,0 +1,572 @@ +""" +AGV妯″瀷 - 鐢ㄤ簬绠楁硶绯荤粺鐨凙GV鏁版嵁寤烘ā +""" +import time +import logging +from typing import Dict, List, Optional, Tuple, Set +from dataclasses import dataclass, field +from enum import Enum + +from common.data_models import AGVStatus, BackpackData +from common.utils import get_coordinate_from_path_id, calculate_manhattan_distance + + +class AGVTaskStatus(Enum): + """AGV浠诲姟鐘舵��""" + IDLE = "IDLE" # 绌洪棽 + ASSIGNED = "ASSIGNED" # 宸插垎閰嶄换鍔′絾鏈紑濮� + EXECUTING = "EXECUTING" # 鎵ц涓� + COMPLETED = "COMPLETED" # 宸插畬鎴� + + +@dataclass +class TaskAssignment: + """浠诲姟鍒嗛厤淇℃伅""" + task_id: str + assigned_time: float + priority: int + status: AGVTaskStatus = AGVTaskStatus.ASSIGNED + start_code: Optional[str] = None + end_code: Optional[str] = None + estimated_duration: Optional[float] = None + + +class AGVModel: + """AGV妯″瀷绫伙紝鐢ㄤ簬绠楁硶璁$畻""" + + def __init__(self, agv_id: str, path_mapping: Dict[str, Dict[str, int]]): + """ + 鍒濆鍖朅GV妯″瀷 + + Args: + agv_id: AGV ID + path_mapping: 璺緞鐐规槧灏勫瓧鍏� + """ + self.agvId = agv_id + self.path_mapping = path_mapping + self.logger = logging.getLogger(__name__) + + # AGV鐘舵�佷俊鎭� + self.status: str = "0" # AGV鐘舵�佺爜 + self.mapCode: str = "" # 褰撳墠浣嶇疆鐮� + self.coordinates: Optional[Tuple[int, int]] = None # 褰撳墠鍧愭爣 + self.backpack: Optional[List[BackpackData]] = None # 鑳屽寘淇℃伅 + + # 鍏呯數鐩稿叧 + self.voltage: int = 100 # 褰撳墠鐢甸噺鐧惧垎姣� + self.autoCharge: int = 20 # 浣庣數閲忚瀹氶槇鍊� + self.lowVol: int = 10 # 鏈�浣庣數閲忛槇鍊� + + # 浠诲姟鐩稿叧 + self.assigned_tasks: List[TaskAssignment] = [] + self.current_task_count: int = 0 + self.max_capacity: int = 5 # 鏈�澶т换鍔″閲� + + # 鎬ц兘鐩稿叧 + self.efficiency_score: float = 1.0 # 鏁堢巼寰楀垎 + self.last_update_time: float = time.time() + + # 缁熻淇℃伅 + self.total_completed_tasks: int = 0 + self.total_distance_traveled: float = 0.0 + self.average_completion_time: float = 0.0 + + def update_from_agv_status(self, agv_status: AGVStatus): + """浠嶢GV鐘舵�佹洿鏂版ā鍨�""" + self.status = agv_status.status + if hasattr(agv_status, 'position'): + self.mapCode = agv_status.position + elif hasattr(agv_status, 'mapCode'): + self.mapCode = agv_status.mapCode + else: + self.mapCode = "" + + self.backpack = agv_status.backpack + self.last_update_time = time.time() + + raw_voltage = getattr(agv_status, 'vol', 100) + raw_auto_charge = getattr(agv_status, 'autoCharge', 20) + raw_low_vol = getattr(agv_status, 'lowVol', 10) + + # 鐢甸噺鏁版嵁鏍囧噯鍖栧鐞� + # 濡傛灉鐢靛帇鍊煎ぇ浜�100锛屽彲鑳芥槸姣紡鍊硷紝闇�瑕佽浆鎹负鐧惧垎姣� + if raw_voltage > 100: + # 鍋囪姝e父鐢靛帇鑼冨洿鏄�3000-5000mV锛岃浆鎹负0-100% + # 杩欓噷浣跨敤绠�鍗曠殑绾挎�ф槧灏� + normalized_voltage = max(0, min(100, ((raw_voltage - 3000) / 2000) * 100)) + self.logger.debug(f"AGV {self.agvId} 鐢靛帇鏍囧噯鍖�: {raw_voltage}mV -> {normalized_voltage:.1f}%") + self.voltage = int(normalized_voltage) + else: + self.voltage = raw_voltage + + # 闃堝�兼爣鍑嗗寲澶勭悊 + if raw_auto_charge > 100: + # 濡傛灉闃堝�间篃鏄浼忓�硷紝鍚屾牱杞崲 + self.autoCharge = max(0, min(100, ((raw_auto_charge - 3000) / 2000) * 100)) + self.logger.debug(f"AGV {self.agvId} 鑷姩鍏呯數闃堝�兼爣鍑嗗寲: {raw_auto_charge}mV -> {self.autoCharge:.1f}%") + else: + self.autoCharge = raw_auto_charge + + if raw_low_vol > 100: + # 濡傛灉鏈�浣庣數閲忛槇鍊间篃鏄浼忓�硷紝鍚屾牱杞崲 + self.lowVol = max(0, min(100, ((raw_low_vol - 3000) / 2000) * 100)) + self.logger.debug(f"AGV {self.agvId} 鏈�浣庣數閲忛槇鍊兼爣鍑嗗寲: {raw_low_vol}mV -> {self.lowVol:.1f}%") + else: + self.lowVol = raw_low_vol + + # 鏇存柊鍧愭爣 + if self.mapCode: + self.coordinates = get_coordinate_from_path_id(self.mapCode, self.path_mapping) + + # 鏍规嵁AGV鐘舵�佹洿鏂颁换鍔¤鏁� + if self.backpack and self.backpack: + self.current_task_count = len([bp for bp in self.backpack if bp.execute]) + else: + self.current_task_count = 0 + + def assign_task(self, task_id: str, priority: int = 5, + start_code: str = "", end_code: str = "") -> bool: + """ + 鍒嗛厤浠诲姟缁橝GV + + Args: + task_id: 浠诲姟ID + priority: 浠诲姟浼樺厛绾� + start_code: 璧峰浣嶇疆鐮� + end_code: 缁撴潫浣嶇疆鐮� + + Returns: + bool: 鏄惁鍒嗛厤鎴愬姛 + """ + if self.is_overloaded(): + self.logger.warning(f"AGV {self.agvId} 宸叉弧杞斤紝鏃犳硶鍒嗛厤鏇村浠诲姟") + return False + + # 鍒涘缓浠诲姟鍒嗛厤璁板綍 + task_assignment = TaskAssignment( + task_id=task_id, + assigned_time=time.time(), + priority=priority, + start_code=start_code, + end_code=end_code + ) + + self.assigned_tasks.append(task_assignment) + self.current_task_count += 1 + + self.logger.info(f"浠诲姟 {task_id} 宸插垎閰嶇粰AGV {self.agvId}") + return True + + def complete_task(self, task_id: str) -> bool: + """ + 瀹屾垚浠诲姟 + + Args: + task_id: 浠诲姟ID + + Returns: + bool: 鏄惁瀹屾垚鎴愬姛 + """ + for task in self.assigned_tasks: + if task.task_id == task_id: + task.status = AGVTaskStatus.COMPLETED + self.current_task_count = max(0, self.current_task_count - 1) + self.total_completed_tasks += 1 + + self.logger.info(f"AGV {self.agvId} 瀹屾垚浠诲姟 {task_id}") + return True + + return False + + def can_accept_task(self, priority: int) -> bool: + """ + 妫�鏌ユ槸鍚﹀彲浠ユ帴鍙楁柊浠诲姟 + + Args: + priority: 浠诲姟浼樺厛绾� + + Returns: + bool: 鏄惁鍙互鎺ュ彈 + """ + # 妫�鏌ュ閲� + if self.is_overloaded(): + self.logger.debug(f"AGV {self.agvId} 浠诲姟宸叉弧杞�({self.current_task_count}/{self.max_capacity})锛屾嫆缁濇柊浠诲姟") + return False + + # 妫�鏌GV鐘舵�侊紙鍏煎鏁存暟鍜屽瓧绗︿覆鏍煎紡锛� + # 鐘舵�� 0(绌洪棽), 1(蹇欑), 2(鍏呯數) 鍙互鎺ュ彈浠诲姟 + # 鐘舵�� 3(鏁呴殰), 4(缁存姢) 涓嶈兘鎺ュ彈浠诲姟 + status_value = str(self.status) # 缁熶竴杞崲涓哄瓧绗︿覆杩涜姣旇緝 + if status_value not in ["0", "1", "2"]: + self.logger.debug(f"AGV {self.agvId} 鐘舵�佸紓甯�(status={status_value})锛屾嫆缁濇柊浠诲姟") + return False + + # 妫�鏌ュ厖鐢电姸鎬� - 濡傛灉鐢甸噺杩囦綆蹇呴』鍏呯數锛屼笉鑳芥帴鍙楁柊浠诲姟 + if self.need_charging(): + self.logger.debug(f"AGV {self.agvId} 鐢甸噺杩囦綆({self.voltage}% <= {self.lowVol}%)锛屽繀椤诲厖鐢碉紝鎷掔粷鏂颁换鍔�") + return False + + # 濡傛灉鏄珮浼樺厛绾т换鍔★紝鍗充娇浣庣數閲忎篃鍙互鎺ュ彈 + if priority >= 9: # 楂樹紭鍏堢骇浠诲姟闃堝�� + self.logger.debug(f"AGV {self.agvId} 鎺ュ彈楂樹紭鍏堢骇浠诲姟(priority={priority})") + return True + + # 瀵逛簬鏅�氫紭鍏堢骇浠诲姟锛屽鏋滅數閲忎綆浣嗕笉鏄繀椤诲厖鐢碉紝鍙互鏍规嵁浼樺厛绾у喅瀹� + if self.can_auto_charge(): + # 浼樺厛绾ц秺楂橈紝瓒婂�惧悜浜庢帴鍙椾换鍔� + if priority >= 5: # 涓珮浼樺厛绾� + self.logger.debug(f"AGV {self.agvId} 鎺ュ彈涓珮浼樺厛绾т换鍔�(priority={priority}, 鐢甸噺={self.voltage}%)") + return True + elif priority >= 3: # 涓瓑浼樺厛绾э紝75%姒傜巼鎺ュ彈 + import random + accept = random.random() < 0.75 + self.logger.debug(f"AGV {self.agvId} 闅忔満鍐冲畾{'鎺ュ彈' if accept else '鎷掔粷'}涓瓑浼樺厛绾т换鍔�(priority={priority}, 鐢甸噺={self.voltage}%)") + return accept + elif priority >= 1: # 浣庝紭鍏堢骇锛�50%姒傜巼鎺ュ彈锛堝寘鎷紭鍏堢骇1锛� + import random + accept = random.random() < 0.5 + self.logger.debug(f"AGV {self.agvId} 闅忔満鍐冲畾{'鎺ュ彈' if accept else '鎷掔粷'}浣庝紭鍏堢骇浠诲姟(priority={priority}, 鐢甸噺={self.voltage}%)") + return accept + else: # 鏋佷綆浼樺厛绾э紙priority=0锛夛紝鎷掔粷 + self.logger.debug(f"AGV {self.agvId} 鐢甸噺鍋忎綆({self.voltage}%)锛屾嫆缁濇瀬浣庝紭鍏堢骇浠诲姟(priority={priority})") + return False + + # 姝e父鎯呭喌涓嬪彲浠ユ帴鍙椾换鍔� + self.logger.debug(f"AGV {self.agvId} 鐘舵�佽壇濂斤紝鍙互鎺ュ彈浠诲姟(鐢甸噺={self.voltage}%, priority={priority})") + return True + + def is_overloaded(self) -> bool: + """ + 妫�鏌ユ槸鍚﹁繃杞� + + Returns: + bool: 鏄惁杩囪浇 + """ + return self.current_task_count >= self.max_capacity + + def get_workload_ratio(self) -> float: + """ + 鑾峰彇宸ヤ綔璐熻浇姣斾緥 + + Returns: + float: 宸ヤ綔璐熻浇姣斾緥 (0.0 - 1.0) + """ + return min(1.0, self.current_task_count / self.max_capacity) + + def get_task_capacity(self) -> int: + """ + 鑾峰彇鍓╀綑浠诲姟瀹归噺 + + Returns: + int: 鍓╀綑瀹归噺 + """ + return max(0, self.max_capacity - self.current_task_count) + + def need_charging(self) -> bool: + """ + 妫�鏌ユ槸鍚﹂渶瑕佸厖鐢� + + Returns: + bool: 鏄惁闇�瑕佸厖鐢� + """ + return self.voltage <= self.lowVol + + def can_auto_charge(self) -> bool: + """ + 妫�鏌ユ槸鍚﹀彲浠ヨ嚜鍔ㄥ厖鐢碉紙浣庝簬闃堝�间絾涓嶆槸蹇呴』鍏呯數锛� + + Returns: + bool: 鏄惁鍙互鑷姩鍏呯數 + """ + return self.lowVol < self.voltage <= self.autoCharge + + def is_low_power(self) -> bool: + """ + 妫�鏌ユ槸鍚︿负浣庣數閲忕姸鎬� + + Returns: + bool: 鏄惁涓轰綆鐢甸噺鐘舵�� + """ + return self.voltage <= self.autoCharge + + def get_charging_priority(self) -> int: + """ + 鑾峰彇鍏呯數浼樺厛绾э紙鏁板�艰秺澶т紭鍏堢骇瓒婇珮锛� + + Returns: + int: 鍏呯數浼樺厛绾� + """ + if self.need_charging(): + return 100 # 蹇呴』鍏呯數锛屾渶楂樹紭鍏堢骇 + elif self.can_auto_charge(): + return 50 + (self.autoCharge - self.voltage) # 鏍规嵁鐢甸噺宸绠椾紭鍏堢骇 + else: + return 0 # 鏃犻渶鍏呯數 + + def calculate_efficiency_score(self, path_mapping: Dict[str, Dict[str, int]]) -> float: + """ + 璁$畻AGV鏁堢巼寰楀垎 + + Args: + path_mapping: 璺緞鐐规槧灏� + + Returns: + float: 鏁堢巼寰楀垎 (0.0 - 1.0) + """ + # 鍩虹鏁堢巼鍒嗘暟 + base_score = 0.7 + + # 鏍规嵁瀹屾垚浠诲姟鏁伴噺璋冩暣 + if self.total_completed_tasks > 0: + completion_bonus = min(0.2, self.total_completed_tasks * 0.01) + base_score += completion_bonus + + # 鏍规嵁璐熻浇鎯呭喌璋冩暣 + load_ratio = self.get_workload_ratio() + if load_ratio < 0.8: # 璐熻浇涓嶅お楂樻椂鏁堢巼鏇撮珮 + load_bonus = (0.8 - load_ratio) * 0.1 + base_score += load_bonus + + # 鏍规嵁AGV鐘舵�佽皟鏁� + if self.status == "0": # 姝e父鐘舵�� + base_score += 0.1 + elif self.status in ["3", "4"]: # 寮傚父鐘舵�� + base_score -= 0.2 + + # 鏃堕棿鍥犲瓙锛氭渶杩戞洿鏂扮殑AGV寰楀垎鏇撮珮 + time_since_update = time.time() - self.last_update_time + if time_since_update < 60: # 1鍒嗛挓鍐呮洿鏂� + time_bonus = (60 - time_since_update) / 600 # 鏈�澶氬姞0.1 + base_score += time_bonus + + return max(0.0, min(1.0, base_score)) + + def estimate_travel_time(self, target_code: str) -> float: + """ + 浼扮畻鍒扮洰鏍囦綅缃殑琛岄┒鏃堕棿 + + Args: + target_code: 鐩爣浣嶇疆鐮� + + Returns: + float: 浼扮畻鏃堕棿锛堢锛� + """ + if not self.coordinates: + return float('inf') + + target_coord = get_coordinate_from_path_id(target_code, self.path_mapping) + if not target_coord: + return float('inf') + + # 璁$畻鏇煎搱椤胯窛绂� + distance = calculate_manhattan_distance(self.coordinates, target_coord) + + # 鍋囪AGV骞冲潎閫熷害涓�1鍗曚綅/绉� + estimated_time = distance * 1.0 + + # 鑰冭檻褰撳墠璐熻浇瀵归�熷害鐨勫奖鍝� + load_factor = 1.0 + (self.get_workload_ratio() * 0.2) + + return estimated_time * load_factor + + def get_task_by_id(self, task_id: str) -> Optional[TaskAssignment]: + """ + 鏍规嵁浠诲姟ID鑾峰彇浠诲姟鍒嗛厤淇℃伅 + + Args: + task_id: 浠诲姟ID + + Returns: + Optional[TaskAssignment]: 浠诲姟鍒嗛厤淇℃伅 + """ + for task in self.assigned_tasks: + if task.task_id == task_id: + return task + return None + + def get_pending_tasks(self) -> List[TaskAssignment]: + """ + 鑾峰彇寰呮墽琛岀殑浠诲姟 + + Returns: + List[TaskAssignment]: 寰呮墽琛屼换鍔″垪琛� + """ + return [task for task in self.assigned_tasks + if task.status in [AGVTaskStatus.ASSIGNED, AGVTaskStatus.EXECUTING]] + + def clear_completed_tasks(self): + """娓呯悊宸插畬鎴愮殑浠诲姟""" + self.assigned_tasks = [task for task in self.assigned_tasks + if task.status != AGVTaskStatus.COMPLETED] + + def __str__(self) -> str: + """瀛楃涓茶〃绀�""" + return (f"AGV({self.agvId}, status={self.status}, " + f"pos={self.mapCode}, tasks={self.current_task_count}/{self.max_capacity})") + + +class AGVModelManager: + """AGV妯″瀷绠$悊鍣�""" + + def __init__(self, path_mapping: Dict[str, Dict[str, int]]): + """ + 鍒濆鍖朅GV妯″瀷绠$悊鍣� + + Args: + path_mapping: 璺緞鐐规槧灏勫瓧鍏� + """ + self.path_mapping = path_mapping + self.agv_models: Dict[str, AGVModel] = {} + self.agv_status_data: Dict[str, AGVStatus] = {} # 瀛樺偍鍘熷AGV鐘舵�佹暟鎹� + self.logger = logging.getLogger(__name__) + + def update_agv_data(self, agv_status_list: List[AGVStatus]): + """ + 鏇存柊AGV鏁版嵁 + + Args: + agv_status_list: AGV鐘舵�佸垪琛� + """ + # 鑾峰彇褰撳墠鏇存柊鐨凙GV ID鍒楄〃 + current_agv_ids = {agv_status.agvId for agv_status in agv_status_list} + + # 绉婚櫎涓嶅啀瀛樺湪鐨凙GV妯″瀷鍜岀姸鎬佹暟鎹� + removed_agvs = [] + for agv_id in list(self.agv_models.keys()): + if agv_id not in current_agv_ids: + removed_agvs.append(agv_id) + del self.agv_models[agv_id] + if agv_id in self.agv_status_data: + del self.agv_status_data[agv_id] + + if removed_agvs: + self.logger.info(f"绉婚櫎涓嶅瓨鍦ㄧ殑AGV: {removed_agvs}") + + # 鏇存柊鎴栧垱寤篈GV妯″瀷鍜岀姸鎬佹暟鎹� + for agv_status in agv_status_list: + agv_id = agv_status.agvId + + # 瀛樺偍鍘熷AGV鐘舵�佹暟鎹� + self.agv_status_data[agv_id] = agv_status + + # 濡傛灉AGV妯″瀷涓嶅瓨鍦紝鍒涘缓鏂扮殑 + if agv_id not in self.agv_models: + self.agv_models[agv_id] = AGVModel(agv_id, self.path_mapping) + self.logger.info(f"鍒涘缓鏂扮殑AGV妯″瀷: {agv_id}") + + # 鏇存柊AGV妯″瀷 + self.agv_models[agv_id].update_from_agv_status(agv_status) + + self.logger.debug(f"鏇存柊浜� {len(agv_status_list)} 涓狝GV妯″瀷锛屽綋鍓嶆�绘暟: {len(self.agv_models)}") + + def get_all_agv_status(self) -> List[AGVStatus]: + """ + 鑾峰彇鎵�鏈堿GV鐨勫師濮嬬姸鎬佹暟鎹� + + Returns: + List[AGVStatus]: AGV鐘舵�佹暟鎹垪琛� + """ + return list(self.agv_status_data.values()) + + def get_agv_status(self, agv_id: str) -> Optional[AGVStatus]: + """ + 鑾峰彇鎸囧畾AGV鐨勫師濮嬬姸鎬佹暟鎹� + + Args: + agv_id: AGV ID + + Returns: + Optional[AGVStatus]: AGV鐘舵�佹暟鎹� + """ + return self.agv_status_data.get(agv_id) + + def get_agv_model(self, agv_id: str) -> Optional[AGVModel]: + """ + 鑾峰彇AGV妯″瀷 + + Args: + agv_id: AGV ID + + Returns: + Optional[AGVModel]: AGV妯″瀷 + """ + return self.agv_models.get(agv_id) + + def get_all_agvs(self) -> List[AGVModel]: + """ + 鑾峰彇鎵�鏈堿GV妯″瀷 + + Returns: + List[AGVModel]: AGV妯″瀷鍒楄〃 + """ + return list(self.agv_models.values()) + + def get_available_agvs(self) -> List[AGVModel]: + """ + 鑾峰彇鍙敤鐨凙GV妯″瀷锛堟湭婊¤浇涓旂姸鎬佹甯革級 + + Returns: + List[AGVModel]: 鍙敤AGV妯″瀷鍒楄〃 + """ + available_agvs = [] + for agv in self.agv_models.values(): + if not agv.is_overloaded() and agv.can_accept_task(5): + available_agvs.append(agv) + + return available_agvs + + def get_agvs_by_status(self, status: str) -> List[AGVModel]: + """ + 鏍规嵁鐘舵�佽幏鍙朅GV鍒楄〃 + + Args: + status: AGV鐘舵�� + + Returns: + List[AGVModel]: 绗﹀悎鐘舵�佺殑AGV鍒楄〃 + """ + return [agv for agv in self.agv_models.values() if agv.status == status] + + def cleanup_old_agvs(self, max_age_seconds: float = 300): + """ + 娓呯悊闀挎椂闂存湭鏇存柊鐨凙GV + + Args: + max_age_seconds: 鏈�澶у厑璁哥殑鏈洿鏂版椂闂达紙绉掞級 + """ + current_time = time.time() + old_agvs = [] + + for agv_id, agv in self.agv_models.items(): + if current_time - agv.last_update_time > max_age_seconds: + old_agvs.append(agv_id) + + for agv_id in old_agvs: + del self.agv_models[agv_id] + self.logger.info(f"娓呯悊闀挎椂闂存湭鏇存柊鐨凙GV: {agv_id}") + + def get_statistics(self) -> Dict[str, any]: + """ + 鑾峰彇AGV缁熻淇℃伅 + + Returns: + Dict: 缁熻淇℃伅 + """ + total_agvs = len(self.agv_models) + available_agvs = len(self.get_available_agvs()) + total_tasks = sum(agv.current_task_count for agv in self.agv_models.values()) + total_capacity = sum(agv.max_capacity for agv in self.agv_models.values()) + + avg_efficiency = 0.0 + if self.agv_models: + avg_efficiency = sum(agv.calculate_efficiency_score(self.path_mapping) + for agv in self.agv_models.values()) / total_agvs + + return { + "total_agvs": total_agvs, + "available_agvs": available_agvs, + "total_assigned_tasks": total_tasks, + "total_capacity": total_capacity, + "capacity_utilization": total_tasks / total_capacity if total_capacity > 0 else 0.0, + "average_efficiency": avg_efficiency + } \ No newline at end of file diff --git a/algorithm_system/path_monitor.py b/algorithm_system/path_monitor.py new file mode 100644 index 0000000..33623db --- /dev/null +++ b/algorithm_system/path_monitor.py @@ -0,0 +1,442 @@ +""" +鐩戞帶RCS绯荤粺鐘舵�佸苟鐢熸垚璺緞 +""" +import time +import threading +import logging +from typing import Dict, List, Optional, Any +import queue + +from common.data_models import AGVStatus, PlannedPath, from_dict +from common.api_client import RCSAPIClient +from algorithm_system.algorithms.path_planning import BatchPathPlanner +from algorithm_system.models.agv_model import AGVModelManager +from common.utils import load_path_mapping, generate_segment_id + +try: + from config.settings import RCS_SERVER_HOST, RCS_SERVER_PORT, MONITOR_POLLING_INTERVAL +except ImportError: + RCS_SERVER_HOST = "10.10.10.156" + RCS_SERVER_PORT = 8088 + MONITOR_POLLING_INTERVAL = 5.0 + logging.warning("鏃犳硶浠巆onfig.settings瀵煎叆閰嶇疆锛屼娇鐢ㄩ粯璁ゅ��") + + +class PathMonitorService: + """鐩戞帶RCS鐘舵�佸苟鐢熸垚璺緞""" + + def __init__(self, + rcs_host: str = None, + rcs_port: int = None, + poll_interval: float = None, + path_algorithm: str = "A_STAR", + auto_send_paths: bool = True): + #鍒濆鍖栬矾寰勭洃鎺ф湇鍔� + self.logger = logging.getLogger(__name__) + + self.rcs_host = rcs_host or RCS_SERVER_HOST + self.rcs_port = rcs_port or RCS_SERVER_PORT + self.poll_interval = poll_interval or MONITOR_POLLING_INTERVAL + self.path_algorithm = path_algorithm + self.auto_send_paths = auto_send_paths + + self.rcs_client = RCSAPIClient(self.rcs_host, self.rcs_port, timeout=10) + self.path_mapping = load_path_mapping() + self.agv_manager = AGVModelManager(self.path_mapping) + + self.path_planner = BatchPathPlanner( + self.path_mapping, + self.path_algorithm, + self.agv_manager + ) + + self.is_running = False + self.monitor_thread = None + self._stop_event = threading.Event() + + self.stats = { + 'poll_count': 0, # 杞娆℃暟 + 'successful_polls': 0, # 鎴愬姛杞娆℃暟 + 'path_generations': 0, # 璺緞鐢熸垚娆℃暟 + 'last_poll_time': None, # 鏈�鍚庤疆璇㈡椂闂� + 'last_generation_time': None # 鏈�鍚庣敓鎴愭椂闂� + } + + self.logger.info(f"璺緞鐩戞帶鏈嶅姟鍒濆鍖栧畬鎴� - 杞闂撮殧: {poll_interval}s, 绠楁硶: {path_algorithm}") + + def start_monitoring(self): + """鍚姩鐩戞帶鏈嶅姟""" + if self.is_running: + self.logger.warning("鐩戞帶鏈嶅姟宸插湪杩愯涓�") + return + + self.is_running = True + self._stop_event.clear() + + self.monitor_thread = threading.Thread( + target=self._monitoring_loop, + name="PathMonitorThread", + daemon=True + ) + self.monitor_thread.start() + + self.logger.info("鐩戞帶鏈嶅姟宸插惎鍔�") + + def stop_monitoring(self): + """鍋滄鐩戞帶鏈嶅姟""" + if not self.is_running: + self.logger.warning("鐩戞帶鏈嶅姟鏈湪杩愯") + return + + self.is_running = False + self._stop_event.set() + + if self.monitor_thread and self.monitor_thread.is_alive(): + self.monitor_thread.join(timeout=5.0) + + self.logger.info("鐩戞帶鏈嶅姟宸插仠姝�") + + def _monitoring_loop(self): + """鐩戞帶涓诲惊鐜�""" + self.logger.info("寮�濮嬬洃鎺у惊鐜�...") + + while self.is_running and not self._stop_event.is_set(): + try: + self._perform_monitoring_cycle() + + if not self._stop_event.wait(self.poll_interval): + continue + else: + break + + except Exception as e: + self.logger.error(f"鐩戞帶寰幆寮傚父: {e}") + self._stop_event.wait(min(self.poll_interval * 2, 10.0)) + + self.logger.info("鐩戞帶寰幆宸茬粨鏉�") + + def _perform_monitoring_cycle(self): + """鎵ц涓�娆$洃鎺у懆鏈�""" + start_time = time.time() + + self.stats['poll_count'] += 1 + self.stats['last_poll_time'] = start_time + + try: + self.logger.info(f"寮�濮嬭矾寰勭洃鎺у懆鏈� #{self.stats['poll_count']}") + + # 1. 鑾峰彇褰撳墠AGV鐘舵�佸拰浠诲姟鐘舵�� + current_agv_status, current_task_status = self._fetch_rcs_status() + + if not current_agv_status: + self.logger.debug("鏈幏鍙栧埌AGV鐘舵�佹暟鎹紝璺宠繃璺緞鐢熸垚") + return + + # 2. 鐩存帴鏍规嵁褰撳墠鐘舵�佺敓鎴愬厤纰版挒璺緞 + self.logger.info(f"[璺緞鐢熸垚] 寮�濮嬩负 {len(current_agv_status)} 涓狝GV鐢熸垚璺緞") + + generated_paths = self._generate_collision_free_paths(current_agv_status) + + # 3. 鍙戦�佽矾寰勫埌RCS锛堟牴鎹厤缃喅瀹氾級 + if generated_paths: + if self.auto_send_paths: + self._send_paths_to_rcs(generated_paths) + else: + self.logger.debug(f"璺緞鐢熸垚瀹屾垚浣嗘湭鍙戦�佸埌RCS - AGV鏁伴噺: {len(current_agv_status)}, 璺緞鏁�: {len(generated_paths)}") + + self.logger.info(f"璺緞鐢熸垚瀹屾垚 - AGV鏁伴噺: {len(current_agv_status)}, 璺緞鏁�: {len(generated_paths)}, 鑷姩鍙戦��: {self.auto_send_paths}") + else: + self.logger.debug("鏈敓鎴愪换浣曡矾寰勶紙鍙兘鎵�鏈堿GV閮藉浜庣┖闂茬姸鎬侊級") + + # 鏇存柊鎴愬姛缁熻 + self.stats['successful_polls'] += 1 + self.stats['path_generations'] += 1 + self.stats['last_generation_time'] = time.time() + + generation_time = (time.time() - start_time) * 1000 + self.logger.debug(f"鐩戞帶鍛ㄦ湡瀹屾垚 - 鑰楁椂: {generation_time:.2f}ms") + + except Exception as e: + self.logger.error(f"鐩戞帶鍛ㄦ湡鎵ц澶辫触: {e}") + + def _generate_unique_seg_id(self, agv_id: str, task_id: str = '') -> str: + """鐢熸垚segId""" + try: + if task_id: + # 鏈変换鍔℃椂浣跨敤task_id + return generate_segment_id(agv_id=agv_id, task_id=task_id, action_type="2") + else: + # 鏃犱换鍔℃椂浣跨敤鏃堕棿鎴充綔涓虹洰鏍囦綅缃紝纭繚涓嶅悓鏃堕棿鏈変笉鍚宻egId + import time + time_target = f"IDLE_{int(time.time() / 3600)}" # 鎸夊皬鏃跺垎缁� + return generate_segment_id(agv_id=agv_id, target_position=time_target, action_type="4") + + except Exception as e: + self.logger.error(f"鐢熸垚segId澶辫触: {e}") + # 闄嶇骇鏂规锛氫娇鐢╟ommon.utils鐨勫嚱鏁扮敓鎴愬鐢↖D + import time + fallback_target = f"FALLBACK_{int(time.time())}" + return generate_segment_id(agv_id=agv_id, target_position=fallback_target, action_type="1") + + def _fetch_rcs_status(self) -> tuple[List[AGVStatus], Dict]: + """浠嶳CS鑾峰彇褰撳墠鐘舵��""" + agv_status_list = [] + task_status = {} + + try: + # 鑾峰彇AGV鐘舵�� - 浣跨敤绌哄弬鏁拌幏鍙栨墍鏈堿GV鐘舵�佸拰浠诲姟鐘舵�� + self.logger.info(" 杞鐩爣: 浣跨敤绌哄弬鏁�(agvId=None, mapId=None)鑾峰彇RCS鎵�鏈堿GV鐘舵�佸拰浠诲姟鐘舵��") + agv_response = self.rcs_client.get_agv_status(agv_id=None, map_id=None) + + if agv_response.code == 200 and agv_response.data: + self.logger.info(f"鎴愬姛鑾峰彇AGV鐘舵�� - 鏁伴噺: {len(agv_response.data)}") + + # 缁熻淇℃伅 + total_tasks = 0 + executing_tasks = 0 + loaded_tasks = 0 + + self.logger.info("=" * 80) + self.logger.info("[璇︾粏AGV鐘舵�佷俊鎭痌") + self.logger.info("=" * 80) + + for i, agv_data in enumerate(agv_response.data, 1): + try: + # 杞崲涓篈GVStatus瀵硅薄 + agv_status = from_dict(AGVStatus, agv_data) + agv_status_list.append(agv_status) + + # 鎵撳嵃AGV鍩烘湰淇℃伅 + self.logger.info(f"[AGV #{i}] {agv_status.agvId}") + self.logger.info(f" 鐘舵��: {agv_status.status} | 浣嶇疆: {agv_status.position} | 鏂瑰悜: {agv_status.direction}") + self.logger.info(f" 鐢靛帇: {agv_status.vol} | 閿欒鐮�: {agv_status.error} | 绌鸿儗绡�: {agv_status.empty}") + + # 鍒嗘瀽鑳岀瘬淇℃伅 + if agv_status.backpack: + self.logger.info(f" [鑳岀瘬淇℃伅] ({len(agv_status.backpack)} 涓�):") + for backpack_item in agv_status.backpack: + status_text = "鎵ц涓�" if backpack_item.execute else ("宸茶杞�" if backpack_item.loaded else "绌洪棽") + task_info = f"浠诲姟: {backpack_item.taskId}" if backpack_item.taskId else "鏃犱换鍔�" + + self.logger.info(f" [浣嶇疆{backpack_item.index}] {task_info} | " + f"鐘舵��: {status_text} | 宸茶杞�: {backpack_item.loaded} | 鎵ц涓�: {backpack_item.execute}") + + # 缁熻浠诲姟 + if backpack_item.taskId: + total_tasks += 1 + if backpack_item.execute: + executing_tasks += 1 + if backpack_item.loaded: + loaded_tasks += 1 + + # 鎻愬彇浠诲姟鐘舵�佷俊鎭� + task_status[backpack_item.taskId] = { + 'agvId': agv_status.agvId, + 'loaded': backpack_item.loaded, + 'execute': backpack_item.execute, + 'index': backpack_item.index + } + else: + self.logger.info(f" [鑳岀瘬淇℃伅] 鏃犺儗绡撲俊鎭�") + + self.logger.info("-" * 60) + except Exception as e: + self.logger.warning(f"瑙f瀽AGV鐘舵�佹暟鎹け璐�: {agv_data} - {e}") + + # 鎵撳嵃缁熻鎽樿 + self.logger.info("[浠诲姟缁熻鎽樿]") + self.logger.info(f" 鎬籄GV鏁伴噺: {len(agv_status_list)}") + self.logger.info(f" 鎬讳换鍔℃暟閲�: {total_tasks}") + self.logger.info(f" 鎵ц涓换鍔�: {executing_tasks}") + self.logger.info(f" 宸茶杞戒换鍔�: {loaded_tasks}") + self.logger.info(f" 鏈杞戒换鍔�: {total_tasks - loaded_tasks}") + self.logger.info("=" * 80) + + else: + self.logger.warning(f"鑾峰彇AGV鐘舵�佸け璐�: {agv_response.msg if agv_response else '鏃犲搷搴�'}") + + except Exception as e: + self.logger.error(f"浠嶳CS鑾峰彇鐘舵�佸け璐�: {e}") + + return agv_status_list, task_status + + + + def _generate_collision_free_paths(self, agv_status_list: List[AGVStatus]) -> List[Dict]: + """涓烘墍鏈堿GV鐢熸垚鍏嶇鎾炶矾寰�""" + try: + self.logger.debug(f"寮�濮嬩负 {len(agv_status_list)} 涓狝GV鐢熸垚鍏嶇鎾炶矾寰�") + + # 浣跨敤鎵归噺璺緞瑙勫垝鍣ㄧ敓鎴愯矾寰� + result = self.path_planner.plan_all_agv_paths( + agv_status_list=agv_status_list, + include_idle_agv=False, # 鍙负鎵ц浠诲姟鐨凙GV鐢熸垚璺緞 + constraints=None + ) + + planned_paths = result.get('plannedPaths', result.get('planned_paths', [])) + + # 璇婃柇锛氭樉绀鸿矾寰勮鍒掔粨鏋滅殑鎵�鏈夊瓧娈� + self.logger.debug(f"璺緞瑙勫垝鍣ㄨ繑鍥炵殑瀛楁: {list(result.keys())}") + self.logger.debug(f"planned_paths瀛楁瀛樺湪: {'planned_paths' in result}") + self.logger.debug(f"plannedPaths瀛楁瀛樺湪: {'plannedPaths' in result}") + self.logger.debug(f"鏈�缁堣幏鍙栧埌鐨勮矾寰勬暟閲�: {len(planned_paths)}") + + if planned_paths: + self.logger.info(f"鎴愬姛鐢熸垚 {len(planned_paths)} 鏉″厤纰版挒璺緞") + + # 璁板綍姣忎釜AGV鐨勮矾寰勪俊鎭� + for path_info in planned_paths: + agv_id = path_info.get('agvId', 'unknown') + code_list = path_info.get('codeList', []) + self.logger.debug(f"AGV {agv_id} 璺緞闀垮害: {len(code_list)}") + else: + self.logger.info("鏈敓鎴愪换浣曡矾寰勶紙鍙兘鎵�鏈堿GV閮藉浜庣┖闂茬姸鎬侊級") + + return planned_paths + + except Exception as e: + self.logger.error(f"鐢熸垚鍏嶇鎾炶矾寰勫け璐�: {e}") + return [] + + + + def _send_paths_to_rcs(self, generated_paths: List[Dict]): + """灏嗙敓鎴愮殑璺緞鎵归噺鍙戦�佸埌RCS绯荤粺""" + try: + self.logger.info(f"寮�濮嬫壒閲忓彂閫� {len(generated_paths)} 鏉¤矾寰勫埌RCS绯荤粺") + + # 鏋勫缓鎵归噺璺緞鏁版嵁 + agv_paths = [] + import uuid + import time as time_module + + for path_info in generated_paths: + agv_id = path_info.get('agvId') + code_list = path_info.get('codeList', []) + + if agv_id and code_list: + path_task_id = '' + for path_code in code_list: + if path_code.get('taskId'): + path_task_id = path_code.get('taskId') + break + + navigation_data = [] + for path_code in code_list: + nav_item = { + 'code': path_code.get('code', ''), + 'direction': path_code.get('direction', '90'), + 'type': path_code.get('type') + } + + if path_code.get('taskId'): + nav_item['taskId'] = path_code.get('taskId') + + if path_code.get('posType'): + nav_item['posType'] = path_code.get('posType') + + if path_code.get('lev') is not None: + nav_item['lev'] = path_code.get('lev') + + navigation_data.append(nav_item) + + auto_seg_id = self._generate_unique_seg_id(agv_id, path_task_id) + + agv_path_data = { + 'agvId': agv_id, + 'segId': auto_seg_id, + 'codeList': navigation_data + } + + agv_paths.append(agv_path_data) + + if not agv_paths: + self.logger.warning("娌℃湁鏈夋晥鐨勮矾寰勬暟鎹渶瑕佸彂閫�") + return + + try: + import requests + import json + + rcs_url = f"http://{self.rcs_host}:{self.rcs_port}/api/open/algorithm/zkd/navigation/v1" + + self.logger.info(f"绠楁硶绯荤粺鍙戦�佽矾寰勮鍒掔粨鏋滃埌RCS:") + self.logger.info(f" AGV鏁伴噺: {len(agv_paths)}") + self.logger.info(f" 鐩爣URL: {rcs_url}") + + successful_sends = 0 + + for agv_path in agv_paths: + agv_id = agv_path['agvId'] + seg_id = agv_path['segId'] + code_list = agv_path['codeList'] + + # 鏋勯�犳爣鍑嗗鑸姹傛暟鎹紙agvId, segId, codeList鏍煎紡锛� + navigation_data = { + 'agvId': agv_id, + 'segId': seg_id, + 'codeList': code_list + } + + # 鏄剧ず璺緞鎽樿 + start_code = code_list[0].get('code', '') if code_list else '' + end_code = code_list[-1].get('code', '') if code_list else '' + task_codes = [nc for nc in code_list if nc.get('taskId')] + task_id = task_codes[0].get('taskId', '') if task_codes else '' + + self.logger.info(f" AGV: {agv_id}, 浠诲姟: {task_id}, 璺緞: {start_code} -> {end_code} ({len(code_list)}鐐�)") + + # 璇︾粏鏄剧ず璺緞JSON鏁版嵁 + self.logger.info(f"鍙戦�佽矾寰凧SON鏁版嵁:") + self.logger.info(json.dumps(navigation_data, ensure_ascii=False, indent=2)) + + try: + response = requests.post( + rcs_url, + json=navigation_data, + timeout=10, + headers={'Content-Type': 'application/json'} + ) + + if response.status_code == 200: + response_data = response.json() + self.logger.info(f" 鎴愬姛鍙戦�佽矾寰勫埌RCS - AGV: {agv_id}, 鍝嶅簲: {response_data.get('msg', 'OK')}") + successful_sends += 1 + else: + self.logger.warning(f" 鍙戦�佽矾寰勫埌RCS澶辫触 - AGV: {agv_id}, HTTP鐘舵��: {response.status_code}") + self.logger.warning(f" 鍝嶅簲鍐呭: {response.text}") + + except Exception as e: + self.logger.warning(f" 鍙戦�佸鑸寚浠ゅ紓甯� - AGV: {agv_id}, 閿欒: {e}") + + self.logger.info(f" 璺緞鍙戦�佸畬鎴� - 鎴愬姛: {successful_sends}/{len(agv_paths)}") + + except Exception as e: + self.logger.error(f" 鍙戦�佸鑸寚浠ゅ紓甯�: {e}") + + except Exception as e: + self.logger.error(f"鍙戦�佽矾寰勫埌RCS寮傚父: {e}") + + def get_monitoring_status(self) -> Dict[str, Any]: + """鑾峰彇鐩戞帶鏈嶅姟鐘舵��""" + return { + 'is_running': self.is_running, + 'rcs_host': self.rcs_host, + 'rcs_port': self.rcs_port, + 'poll_interval': self.poll_interval, + 'path_algorithm': self.path_algorithm, + 'auto_send_paths': self.auto_send_paths, + 'stats': self.stats.copy() + } + + def reset_stats(self): + """閲嶇疆缁熻淇℃伅""" + self.stats = { + 'poll_count': 0, + 'successful_polls': 0, + 'path_generations': 0, + 'last_poll_time': None, + 'last_generation_time': None + } + self.logger.info("鐩戞帶缁熻淇℃伅宸查噸缃�") \ No newline at end of file diff --git a/common/__init__.py b/common/__init__.py new file mode 100644 index 0000000..cee7d4f --- /dev/null +++ b/common/__init__.py @@ -0,0 +1,2 @@ +# Common Components Module +__version__ = "1.0.0" \ No newline at end of file diff --git a/common/api_client.py b/common/api_client.py new file mode 100644 index 0000000..ffc818e --- /dev/null +++ b/common/api_client.py @@ -0,0 +1,124 @@ +""" +涓嶳CS绯荤粺閫氫俊 +""" +import requests +import json +import logging +import time +from typing import Dict, Optional, Any +from .data_models import ( + APIResponse, create_error_response, ResponseCode +) + +try: + from config.settings import ( + RCS_SERVER_HOST, RCS_SERVER_PORT, + REQUEST_TIMEOUT, AGV_STATUS_API_ENDPOINT + ) +except ImportError: + RCS_SERVER_HOST = "10.10.10.156" + RCS_SERVER_PORT = 8088 + REQUEST_TIMEOUT = 30 + AGV_STATUS_API_ENDPOINT = "/api/open/algorithm/getAgv" + logging.warning("鏃犳硶浠巆onfig.settings瀵煎叆閰嶇疆锛屼娇鐢ㄩ粯璁ゅ��") + + +class APIClient: + """HTTP API瀹㈡埛绔熀绫�""" + + def __init__(self, base_url: str, timeout: int = 30): + """鍒濆鍖朅PI瀹㈡埛绔�""" + self.base_url = base_url.rstrip('/') + self.timeout = timeout + self.logger = logging.getLogger(__name__) + self.session = requests.Session() + + self.session.headers.update({ + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }) + + def _make_request(self, method: str, endpoint: str, data: Any = None, + params: Dict = None) -> APIResponse: + """鍙戦�丠TTP璇锋眰""" + url = f"{self.base_url}{endpoint}" + + try: + start_time = time.time() + + request_kwargs = { + 'timeout': self.timeout, + 'params': params + } + + if data is not None: + if isinstance(data, (dict, list)): + request_kwargs['json'] = data + else: + request_kwargs['data'] = json.dumps(data) + + response = self.session.request(method, url, **request_kwargs) + + end_time = time.time() + duration = (end_time - start_time) * 1000 # 杞崲涓烘绉� + + self.logger.info(f"{method} {url} - {response.status_code} - {duration:.2f}ms") + + try: + response_data = response.json() + except ValueError: + response_data = {"text": response.text} + + if response.status_code == 200: + if isinstance(response_data, dict) and 'code' in response_data: + return APIResponse( + code=response_data.get('code', ResponseCode.SUCCESS), + msg=response_data.get('msg', '鎿嶄綔鎴愬姛'), + data=response_data.get('data') + ) + else: + return APIResponse( + code=ResponseCode.SUCCESS, + msg='鎿嶄綔鎴愬姛', + data=response_data + ) + else: + return create_error_response( + ResponseCode.SERVER_ERROR, + f"HTTP {response.status_code}: {response.text}" + ) + + except requests.exceptions.Timeout: + self.logger.error(f"璇锋眰瓒呮椂: {url}") + return create_error_response(ResponseCode.SERVER_ERROR, "璇锋眰瓒呮椂") + + except requests.exceptions.ConnectionError: + self.logger.error(f"杩炴帴閿欒: {url}") + return create_error_response(ResponseCode.SERVER_ERROR, "杩炴帴閿欒") + + except Exception as e: + self.logger.error(f"璇锋眰寮傚父: {url} - {str(e)}") + return create_error_response(ResponseCode.SERVER_ERROR, f"璇锋眰寮傚父: {str(e)}") + + +class RCSAPIClient(APIClient): + + def __init__(self, rcs_host: str = None, rcs_port: int = None, timeout: int = None): + """鍒濆鍖朢CS API瀹㈡埛绔�""" + rcs_host = rcs_host or RCS_SERVER_HOST + rcs_port = rcs_port or RCS_SERVER_PORT + timeout = timeout or REQUEST_TIMEOUT + + base_url = f"http://{rcs_host}:{rcs_port}" + super().__init__(base_url, timeout) + + def get_agv_status(self, agv_id: Optional[str] = None, + map_id: Optional[str] = None) -> APIResponse: + """鑾峰彇AGV鐘舵��""" + data = {} + if agv_id: + data['agvId'] = agv_id + if map_id: + data['mapId'] = map_id + + return self._make_request('POST', AGV_STATUS_API_ENDPOINT, data=data) \ No newline at end of file diff --git a/common/data_models.py b/common/data_models.py new file mode 100644 index 0000000..132df1e --- /dev/null +++ b/common/data_models.py @@ -0,0 +1,184 @@ +""" +鏁版嵁妯″瀷瀹氫箟 +""" +from dataclasses import dataclass, field +from typing import List, Optional, Dict, Any +from enum import Enum +import json + + +class AGVStatusEnum(Enum): + # AGV鐘舵�� + IDLE = 0 # 绌洪棽 + BUSY = 1 # 蹇欑 + CHARGING = 2 # 鍏呯數 + ERROR = 3 # 鏁呴殰 + MAINTENANCE = 4 # 缁存姢 + + +class TaskTypeEnum(Enum): + # 浠诲姟绫诲瀷 + PICKUP = "1" # 鍙栬揣 + DELIVERY = "2" # 閫佽揣 + TRANSPORT = "3" # 杩愯緭 + + +class AGVActionTypeEnum(Enum): + # AGV鍔ㄤ綔绫诲瀷 + AVOIDANCE = "1" # 閬胯 + TASK = "2" # 浠诲姟 + CHARGING = "3" # 鍏呯數 + STANDBY = "4" # 鍘诲緟鏈轰綅 + + +@dataclass +class BackpackData: + # 鑳岀瘬鏁版嵁 + index: int # 鑳岀瘬缂栧彿 + loaded: bool # 鏄惁杞借揣 + execute: bool # 鏄惁鍦ㄦ墽琛� + taskId: Optional[str] = None # 鎵ц浠诲姟缂栧彿 + + +@dataclass +class AGVStatus: + # AGV鐘舵�� + agvId: str # 灏忚溅缂栧彿 + status: int # 鐘舵�� + position: str # 灏忚溅褰撳墠鐐逛綅 + empty: int # 绌鸿儗绡撴暟閲� + direction: str # 灏忚溅瑙掑害 + vol: int # 鐢靛帇 + error: int # 寮傚父鐮侊紝0琛ㄧず姝e父 + backpack: List[BackpackData] = field(default_factory=list) # 鑳岀瘬鏁版嵁 + autoCharge: int = 20 # 浣庣數閲忚瀹氶槇鍊硷紝浣庝簬璇ュ�煎彲浠ュ幓鑷姩鍏呯數涔熷彲浠ョ户缁仛浠诲姟 + lowVol: int = 10 # 鏈�浣庣數閲忥紝鐢甸噺浣庝簬璇ュ�煎繀椤诲幓鍏呯數 + + +@dataclass +class TaskData: + # 浠诲姟鏁版嵁 + taskId: str # 浠诲姟id + start: str # 璧风偣 + end: str # 缁堢偣 + type: str # 浠诲姟绫诲瀷 + priority: int # 浼樺厛绾� + + +@dataclass +class PathCode: + # 璺緞鐐� + code: str # 鍦板浘鐐逛綅id + direction: str # 鏂瑰悜 + type: Optional[str] = None # AGV鍔ㄤ綔绫诲瀷锛堥伩璁┿�佷换鍔°�佸厖鐢点�佸幓寰呮満浣嶏級 + taskId: Optional[str] = None # 浠诲姟缂栧彿锛屽鏋滄槸鎵ц浠诲姟鍒欏繀闇� + posType: Optional[str] = None # 鍔ㄤ綔绫诲瀷锛岃〃绀哄埌杈炬煇涓偣浣嶈繘琛岀殑鍔ㄤ綔锛屽鍙栥�佹斁 + lev: Optional[int] = None # 琛ㄧずposType瀵瑰簲鐨勪换鍔℃墍瑕佹搷浣滅殑鏄鍑犱釜鑳岀瘬 + + +@dataclass +class PlannedPath: + # 瑙勫垝璺緞 + agvId: str # 灏忚溅缂栧彿 + codeList: List[PathCode] # 鐐逛綅闆嗗悎 + segId: Optional[str] = None # 瀵艰埅閲嶅鍙戦�佹椂鐨勫幓閲嶆爣璇� + + +@dataclass +class TaskAssignment: + # 浠诲姟鍒嗛厤缁撴灉 + taskId: str # 浠诲姟ID + agvId: str # 鍒嗛厤鐨凙GV ID + lev_id: int = 0 # 鑳岀瘬浣嶇疆缂栧彿 + + +@dataclass +class APIResponse: + # API鍝嶅簲鏍煎紡 - 瀛楁椤哄簭鍥哄畾涓� code, msg, data + code: int # 鐘舵�佺爜 + msg: str # 娑堟伅 + data: Optional[Any] = None # 鏁版嵁 + + def to_ordered_dict(self) -> Dict: + # 杞崲涓烘湁搴忓瓧鍏� + from collections import OrderedDict + return OrderedDict([ + ('code', self.code), + ('msg', self.msg), + ('data', self.data) + ]) + + +class ResponseCode: + # 鍝嶅簲鐘舵�佺爜 + SUCCESS = 200 # 鎿嶄綔鎴愬姛 + NO_DATA = 201 # 鏆傛棤鏁版嵁 + PARAM_EMPTY = 401 # 鍙傛暟涓虹┖ + PERMISSION_DENIED = 403 # 鏉冮檺涓嶈冻 + DUPLICATE_SUBMIT = 407 # 璇峰嬁閲嶅鎻愪氦 + SERVER_ERROR = 500 # 鏈嶅姟鍣ㄩ敊璇� + + +def create_success_response(data: Any = None, msg: str = "鎿嶄綔鎴愬姛") -> APIResponse: + # 鍒涘缓鎴愬姛鍝嶅簲 + return APIResponse(code=ResponseCode.SUCCESS, msg=msg, data=data) + +def create_error_response(code: int, msg: str) -> APIResponse: + # 鍒涘缓閿欒鍝嶅簲 + return APIResponse(code=code, msg=msg, data=None) + +def to_dict(obj) -> Dict: + # 灏嗘暟鎹被杞崲涓哄瓧鍏� + if hasattr(obj, '__dict__'): + if isinstance(obj, APIResponse): + return obj.to_ordered_dict() + + result = {} + for key, value in obj.__dict__.items(): + if isinstance(value, list): + result[key] = [to_dict(item) if hasattr(item, '__dict__') else item for item in value] + elif hasattr(value, '__dict__'): + result[key] = to_dict(value) + else: + result[key] = value + return result + return obj + + +def from_dict(data_class, data: Dict): + # 浠庡瓧鍏稿垱寤烘暟鎹被瀹炰緥 + if isinstance(data, dict): + field_types = data_class.__annotations__ + kwargs = {} + + for field_name, field_type in field_types.items(): + if field_name in data: + value = data[field_name] + + if hasattr(field_type, '__origin__') and field_type.__origin__ is list: + inner_type = field_type.__args__[0] + if hasattr(inner_type, '__annotations__'): + kwargs[field_name] = [from_dict(inner_type, item) for item in value] + else: + kwargs[field_name] = value + elif hasattr(field_type, '__origin__'): + import typing + origin = getattr(field_type, '__origin__', None) + + if origin is typing.Union or str(origin) == 'typing.Union': + kwargs[field_name] = value + else: + kwargs[field_name] = value + elif hasattr(field_type, '__annotations__'): + kwargs[field_name] = from_dict(field_type, value) + else: + kwargs[field_name] = value + else: + field_info = data_class.__dataclass_fields__.get(field_name) + if field_info and field_info.default is not field_info.default_factory: + pass + elif field_info and field_info.default_factory is not field_info.default_factory: + pass + + return data_class(**kwargs) + return data \ No newline at end of file diff --git a/common/utils.py b/common/utils.py new file mode 100644 index 0000000..6340727 --- /dev/null +++ b/common/utils.py @@ -0,0 +1,286 @@ +import json +import logging +import os +import random +import time +import uuid +import string +from typing import Dict, List, Tuple, Optional, Any +from datetime import datetime + + +def setup_logging(log_level: str = "INFO", log_file: Optional[str] = None) -> None: + """璁剧疆鏃ュ織閰嶇疆""" + log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + formatter = logging.Formatter(log_format) + + root_logger = logging.getLogger() + root_logger.setLevel(getattr(logging, log_level.upper())) + + for handler in root_logger.handlers[:]: + root_logger.removeHandler(handler) + + console_handler = logging.StreamHandler() + console_handler.setFormatter(formatter) + root_logger.addHandler(console_handler) + + if log_file: + file_handler = logging.FileHandler(log_file, encoding='utf-8') + file_handler.setFormatter(formatter) + root_logger.addHandler(file_handler) + + +def load_path_mapping(mapping_file: str = "path_mapping.json") -> Dict[str, Dict[str, int]]: + """鍔犺浇璺緞鏄犲皠鏂囦欢""" + logger = logging.getLogger(__name__) + + try: + with open(mapping_file, 'r', encoding='utf-8') as f: + data = json.load(f) + + path_mapping = {} + if "path_id_to_coordinates" in data: + for path_id, coordinates in data["path_id_to_coordinates"].items(): + if coordinates and len(coordinates) > 0: + coord = coordinates[0] + path_mapping[path_id] = { + "x": coord["x"], + "y": coord["y"] + } + + logger.info(f"鎴愬姛鍔犺浇璺緞鏄犲皠锛屽叡 {len(path_mapping)} 涓矾寰勭偣") + return path_mapping + + except FileNotFoundError: + logger.error(f"璺緞鏄犲皠鏂囦欢涓嶅瓨鍦�: {mapping_file}") + return {} + except json.JSONDecodeError as e: + logger.error(f"璺緞鏄犲皠鏂囦欢鏍煎紡閿欒: {e}") + return {} + except Exception as e: + logger.error(f"鍔犺浇璺緞鏄犲皠鏂囦欢澶辫触: {e}") + return {} + + +def get_coordinate_from_path_id(path_id: str, path_mapping: Dict[str, Dict[str, int]]) -> Optional[Tuple[int, int]]: + """ + 鏍规嵁璺緞鐐笽D鑾峰彇鍧愭爣 + + Args: + path_id: 璺緞鐐笽D锛堟敮鎸佸甫8浣嶅甫闆剁殑鏍煎紡锛宔.g.'00000206'锛� + path_mapping: 璺緞鏄犲皠瀛楀吀 + + Returns: + Optional[Tuple[int, int]]: 鍧愭爣(x, y)锛屽鏋滄壘涓嶅埌鍒欒繑鍥濶one + """ + logger = logging.getLogger(__name__) + + if path_id in path_mapping: + coord = path_mapping[path_id] + logger.debug(f"璺緞ID {path_id} 鍖归厤鎴愬姛锛屽潗鏍�: ({coord['x']}, {coord['y']})") + return (coord["x"], coord["y"]) + + # 濡傛灉鍖归厤澶辫触锛屽皾璇曞幓鎺夐浂鍚庡尮閰� + try: + normalized_path_id = str(int(path_id)) + if normalized_path_id in path_mapping: + coord = path_mapping[normalized_path_id] + logger.debug(f"璺緞ID {path_id} 瑙勮寖鍖栦负 {normalized_path_id} 鍚庡尮閰嶆垚鍔燂紝鍧愭爣: ({coord['x']}, {coord['y']})") + return (coord["x"], coord["y"]) + else: + logger.warning(f"瑙勮寖鍖栧悗鐨勮矾寰処D {normalized_path_id}锛堝師濮�: {path_id}锛夊湪璺緞鏄犲皠涓湭鎵惧埌") + except (ValueError, TypeError): + logger.warning(f"璺緞ID {path_id} 涓嶆槸鏈夋晥鐨勬暟瀛楁牸寮�") + + logger.warning(f"鏃犳硶鎵惧埌璺緞ID {path_id} 瀵瑰簲鐨勫潗鏍�") + return None + + +def get_path_id_from_coordinate(x: int, y: int, path_mapping: Dict[str, Dict[str, int]]) -> Optional[str]: + """鏍规嵁鍧愭爣鑾峰彇璺緞鐐笽D""" + for path_id, coord in path_mapping.items(): + if coord["x"] == x and coord["y"] == y: + return path_id + return None + + +def calculate_distance(pos1: Tuple[int, int], pos2: Tuple[int, int]) -> float: + """璁$畻涓ょ偣涔嬮棿鐨勬姘忚窛绂�""" + return ((pos1[0] - pos2[0]) ** 2 + (pos1[1] - pos2[1]) ** 2) ** 0.5 + + +def calculate_manhattan_distance(pos1: Tuple[int, int], pos2: Tuple[int, int]) -> int: + """璁$畻涓ょ偣涔嬮棿鐨勬浖鍝堥】璺濈""" + return abs(pos1[0] - pos2[0]) + abs(pos1[1] - pos2[1]) + + +def generate_random_agv_id() -> str: + """鐢熸垚闅忔満AGV ID""" + return f"AGV_{random.randint(10001, 99999)}" + + +def generate_random_task_id() -> str: + """鐢熸垚闅忔満浠诲姟ID""" + timestamp = int(time.time() * 1000) # 姣鏃堕棿鎴� + random_suffix = random.randint(100, 999) + return f"TASK_{timestamp}_{random_suffix}" + + +def get_random_path_ids(path_mapping: Dict[str, Dict[str, int]], count: int = 2) -> List[str]: + """闅忔満鑾峰彇璺緞鐐笽D""" + if not path_mapping: + return [] + + path_ids = list(path_mapping.keys()) + if len(path_ids) < count: + return path_ids + + return random.sample(path_ids, count) + + +def format_timestamp(timestamp: Optional[float] = None) -> str: + """鏍煎紡鍖栨椂闂存埑""" + if timestamp is None: + timestamp = time.time() + + return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S") + + +def normalize_path_id(path_id: str) -> str: + """鏍囧噯鍖栬矾寰勭偣ID鏍煎紡""" + if not path_id: + return path_id + + try: + normalized_path_id = str(int(path_id)) + return normalized_path_id + except (ValueError, TypeError): + return path_id + + +def validate_path_id(path_id: str) -> bool: + """楠岃瘉璺緞鐐笽D鏍煎紡""" + try: + # 璺緞鐐笽D搴旇鏄暟瀛楀瓧绗︿覆锛屽湪1-1696鑼冨洿鍐� + path_num = int(path_id) + return 1 <= path_num <= 1696 + except (ValueError, TypeError): + return False + + +def validate_agv_id(agv_id: str) -> bool: + """楠岃瘉AGV ID鏍煎紡""" + if not agv_id or not isinstance(agv_id, str): + return False + # AGV ID涓嶈兘涓虹┖涓旈暱搴﹀悎鐞� + return len(agv_id.strip()) > 0 and len(agv_id) <= 50 + + +def save_json_file(data: Any, file_path: str) -> bool: + """淇濆瓨鏁版嵁鍒癑SON""" + logger = logging.getLogger(__name__) + + try: + with open(file_path, 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False, indent=2) + + logger.info(f"鏁版嵁宸蹭繚瀛樺埌鏂囦欢: {file_path}") + return True + + except Exception as e: + logger.error(f"淇濆瓨鏂囦欢澶辫触: {file_path} - {e}") + return False + + +def load_json_file(file_path: str) -> Optional[Any]: + """浠嶫SON鏂囦欢鍔犺浇鏁版嵁""" + logger = logging.getLogger(__name__) + + try: + with open(file_path, 'r', encoding='utf-8') as f: + data = json.load(f) + + logger.info(f"鏁版嵁宸蹭粠鏂囦欢鍔犺浇: {file_path}") + return data + + except FileNotFoundError: + logger.warning(f"鏂囦欢涓嶅瓨鍦�: {file_path}") + return None + except json.JSONDecodeError as e: + logger.error(f"JSON鏍煎紡閿欒: {file_path} - {e}") + return None + except Exception as e: + logger.error(f"鍔犺浇鏂囦欢澶辫触: {file_path} - {e}") + return None + + +def ensure_directory_exists(directory: str) -> bool: + """纭繚鐩綍瀛樺湪锛屽鏋滀笉瀛樺湪鍒欏垱寤�""" + try: + os.makedirs(directory, exist_ok=True) + return True + except Exception as e: + logging.getLogger(__name__).error(f"鍒涘缓鐩綍澶辫触: {directory} - {e}") + return False + + +def generate_segment_id(agv_id: str, task_id: Optional[str] = None, + target_position: Optional[str] = None, + action_type: str = "2") -> str: + """ + 鐢熸垚瀵艰埅娈礗D锛岀敤浜庡幓閲嶆爣璇� + 鍩轰簬AGV ID銆佷换鍔D銆佺洰鏍囦綅缃拰鍔ㄤ綔绫诲瀷鐢熸垚鍥哄畾鐨�11浣岻D + + Args: + agv_id: AGV ID + task_id: 浠诲姟ID + target_position: 鐩爣浣嶇疆 + action_type: 鍔ㄤ綔绫诲瀷 + + Returns: + str: 11浣嶅鑸ID + """ + import hashlib + + # 鏋勫缓鐢ㄤ簬鍝堝笇鐨勫瓧绗︿覆 + if task_id: + # 浠诲姟绫诲瀷锛氫娇鐢╝gv_id + task_id + hash_input = f"{agv_id}_{task_id}_{action_type}" + else: + # 闈炰换鍔$被鍨嬶細浣跨敤agv_id + target_position + action_type + target = target_position or "unknown" + hash_input = f"{agv_id}_{target}_{action_type}" + + # 浣跨敤MD5鍝堝笇鐢熸垚鍥哄畾鍊� + hash_object = hashlib.md5(hash_input.encode()) + hash_hex = hash_object.hexdigest() + + # 灏�16杩涘埗杞崲涓烘暟瀛楀苟鎴彇11浣� + hash_int = int(hash_hex[:8], 16) + seg_id = str(hash_int)[-11:].zfill(11) + + return seg_id + + +def generate_navigation_code(code: str, direction: str, action_type: str = "2", + task_id: Optional[str] = None, pos_type: Optional[str] = None, + backpack_level: Optional[int] = None, is_target_point: bool = False) -> Dict: + """鐢熸垚瀵艰埅璺緞鐐逛唬鐮�""" + path_code = { + 'code': code, + 'direction': direction, + 'type': action_type + } + + if task_id and action_type == "2": + path_code['taskId'] = task_id + + # 鍙湁鍒拌揪鐩爣鐐规椂鎵嶆坊鍔爌osType鍜宭ev锛屼笖鏈夊�兼椂鎵嶅寘鍚瓧娈� + if is_target_point: + if pos_type: + path_code['posType'] = pos_type + + if backpack_level is not None: + path_code['lev'] = backpack_level + + return path_code \ No newline at end of file diff --git a/config/environment.py b/config/environment.py new file mode 100644 index 0000000..47f42d0 --- /dev/null +++ b/config/environment.py @@ -0,0 +1,360 @@ +""" +鐜閰嶇疆妯″潡 - 璐熻矗瀹氫箟鍔犺浇浠撳簱鐜閰嶇疆 +""" +import json +from enum import Enum, auto +import numpy as np +from typing import Dict, List, Tuple, Optional, Union + + +class CellType(Enum): + """鍗曞厓鏍肩被鍨嬫灇涓�""" + EMPTY = auto() # 绌哄崟鍏冩牸 + STATION = auto() # 绔欑偣 + PATH = auto() # 璺緞 + STORAGE = auto() # 浠撳偍鍖哄煙 + OBSTACLE = auto() # 闅滅鐗� + LOADING = auto() # 瑁呰浇鍖� + UNLOADING = auto() # 鍗歌浇鍖� + + +class EnvironmentConfig: + """鐜閰嶇疆绫�""" + + def __init__(self, width: int, height: int): + """鍒濆鍖栫幆澧冮厤缃�""" + self.width = width + self.height = height + # 鍒濆鍖栨墍鏈夊崟鍏冩牸涓虹┖ + self.grid = np.full((height, width), CellType.EMPTY) + # 绔欑偣浣嶇疆鍙婂叾閰嶇疆 + self.stations = {} # 鏍煎紡: {(x, y): {"capacity": 4, "load_position": (x-1, y), "unload_position": (x+1, y)}} + # 璺緞鐐逛綅缃� + self.paths = set() # 鏍煎紡: {(x1, y1), (x2, y2), ...} + # 浠撳偍鍖哄煙浣嶇疆 + self.storage_areas = set() # 鏍煎紡: {(x1, y1), (x2, y2), ...} + # 瑁呭嵏鍖轰綅缃� + self.loading_areas = set() # 鏍煎紡: {(x1, y1), (x2, y2), ...} + self.unloading_areas = set() # 鏍煎紡: {(x1, y1), (x2, y2), ...} + # 闅滅鐗╀綅缃� + self.obstacles = set() # 鏍煎紡: {(x1, y1), (x2, y2), ...} + # 缃戞牸鐐归偦鎺ヨ〃锛堢敤浜庤矾寰勮鍒掞級 + self.adjacency_list = {} # 鏍煎紡: {(x1, y1): {(x2, y2): distance, ...}, ...} + + def set_cell_type(self, x: int, y: int, cell_type: CellType) -> bool: + """ + 璁剧疆鍗曞厓鏍肩被鍨� + + Args: + x: x鍧愭爣 + y: y鍧愭爣 + cell_type: 鍗曞厓鏍肩被鍨� + + Returns: + bool: 璁剧疆鏄惁鎴愬姛 + """ + if 0 <= x < self.width and 0 <= y < self.height: + self.grid[y, x] = cell_type + + # 鏍规嵁鍗曞厓鏍肩被鍨嬫洿鏂扮浉搴旂殑闆嗗悎 + pos = (x, y) + if cell_type == CellType.PATH: + self.paths.add(pos) + elif cell_type == CellType.STATION: + if pos not in self.stations: + self.stations[pos] = { + "capacity": 4, # 榛樿瀹归噺 + "load_position": (x-1, y), # 榛樿鍔犺浇浣嶇疆 + "unload_position": (x+1, y) # 榛樿鍗歌浇浣嶇疆 + } + elif cell_type == CellType.STORAGE: + self.storage_areas.add(pos) + elif cell_type == CellType.LOADING: + self.loading_areas.add(pos) + elif cell_type == CellType.UNLOADING: + self.unloading_areas.add(pos) + elif cell_type == CellType.OBSTACLE: + self.obstacles.add(pos) + + return True + return False + + def add_station(self, x: int, y: int, capacity: int = 4, + load_position: Tuple[int, int] = None, + unload_position: Tuple[int, int] = None) -> bool: + """ + 娣诲姞绔欑偣 + + Args: + x: 绔欑偣x鍧愭爣 + y: 绔欑偣y鍧愭爣 + capacity: 绔欑偣瀹归噺 + load_position: 鍔犺浇浣嶇疆 + unload_position: 鍗歌浇浣嶇疆 + + Returns: + bool: 娣诲姞鏄惁鎴愬姛 + """ + if 0 <= x < self.width and 0 <= y < self.height: + # 璁剧疆榛樿鐨勮鍗镐綅缃� + if load_position is None: + load_position = (x-1, y) # 宸︿晶涓哄嵏鏂欑浣嶇疆 + if unload_position is None: + unload_position = (x+1, y) # 鍙充晶涓哄彇鏂欑浣嶇疆 + + # 鏇存柊缃戞牸鍜岀珯鐐逛俊鎭� + self.grid[y, x] = CellType.STATION + self.stations[(x, y)] = { + "capacity": capacity, + "load_position": load_position, + "unload_position": unload_position + } + + # 纭繚瑁呭嵏浣嶇疆涔熻鏍囪 + self.set_cell_type(load_position[0], load_position[1], CellType.LOADING) + self.set_cell_type(unload_position[0], unload_position[1], CellType.UNLOADING) + + return True + return False + + def add_path(self, x: int, y: int) -> bool: + """ + 娣诲姞璺緞鐐� + + Args: + x: x鍧愭爣 + y: y鍧愭爣 + + Returns: + bool: 娣诲姞鏄惁鎴愬姛 + """ + return self.set_cell_type(x, y, CellType.PATH) + + def add_storage_area(self, x: int, y: int) -> bool: + """ + 娣诲姞浠撳偍鍖哄煙 + + Args: + x: x鍧愭爣 + y: y鍧愭爣 + + Returns: + bool: 娣诲姞鏄惁鎴愬姛 + """ + return self.set_cell_type(x, y, CellType.STORAGE) + + def add_obstacle(self, x: int, y: int) -> bool: + """ + 娣诲姞闅滅鐗� + + Args: + x: x鍧愭爣 + y: y鍧愭爣 + + Returns: + bool: 娣诲姞鏄惁鎴愬姛 + """ + return self.set_cell_type(x, y, CellType.OBSTACLE) + + def is_valid_position(self, x: int, y: int) -> bool: + """ + 妫�鏌ヤ綅缃槸鍚﹀悎娉曪紙鍦ㄧ綉鏍煎唴涓旈潪闅滅鐗╋級 + + Args: + x: x鍧愭爣 + y: y鍧愭爣 + + Returns: + bool: 浣嶇疆鏄惁鍚堟硶 + """ + return (0 <= x < self.width and + 0 <= y < self.height and + self.grid[y, x] != CellType.OBSTACLE) + + def build_adjacency_list(self): + """鏋勫缓缃戞牸鐐归偦鎺ヨ〃锛堢敤浜庤矾寰勮鍒掞級""" + # 娓呯┖鐜版湁閭绘帴琛� + self.adjacency_list = {} + + # AGV杩涜鍥涙柟鍚戠Щ鍔� + directions = [ + (0, 1), # 涓� + (1, 0), # 鍙� + (0, -1), # 涓� + (-1, 0) # 宸� + ] + + # 涓烘瘡涓矾寰勭偣鏋勫缓閭绘帴琛� + for x, y in self.paths: + if (x, y) not in self.adjacency_list: + self.adjacency_list[(x, y)] = {} + + # 妫�鏌ュ洓涓柟鍚戠殑閭诲眳 + for dx, dy in directions: + nx, ny = x + dx, y + dy + # 妫�鏌ョ浉閭荤偣鏄惁涓烘湁鏁堣矾寰勭偣 + if (nx, ny) in self.paths: + # 绉诲姩璺濈缁熶竴涓�1.0 + distance = 1.0 + self.adjacency_list[(x, y)][(nx, ny)] = distance + + # 娣诲姞绔欑偣鐨勮鍗哥偣鍒伴偦鎺ヨ〃 + for station_pos, station_info in self.stations.items(): + load_pos = station_info["load_position"] + unload_pos = station_info["unload_position"] + + # 纭繚瑁呭嵏鐐瑰凡娣诲姞鍒伴偦鎺ヨ〃 + if load_pos not in self.adjacency_list: + self.adjacency_list[load_pos] = {} + if unload_pos not in self.adjacency_list: + self.adjacency_list[unload_pos] = {} + + # 杩炴帴瑁呭嵏鐐瑰埌鏈�杩戠殑璺緞鐐� + for pos in [load_pos, unload_pos]: + # 鎵惧埌鏈�杩戠殑璺緞鐐瑰苟杩炴帴 + min_dist = float('inf') + nearest_path = None + + for path_pos in self.paths: + dist = ((pos[0] - path_pos[0]) ** 2 + (pos[1] - path_pos[1]) ** 2) ** 0.5 + if dist < min_dist: + min_dist = dist + nearest_path = path_pos + + if nearest_path: + self.adjacency_list[pos][nearest_path] = min_dist + self.adjacency_list[nearest_path][pos] = min_dist + + def save_to_file(self, filepath: str): + """ + 灏嗙幆澧冮厤缃繚瀛樺埌鏂囦欢 + + Args: + filepath: 鏂囦欢璺緞 + """ + config_data = { + "width": self.width, + "height": self.height, + "stations": {f"{x},{y}": info for (x, y), info in self.stations.items()}, + "paths": [{"x": x, "y": y} for x, y in self.paths], + "storage_areas": [{"x": x, "y": y} for x, y in self.storage_areas], + "loading_areas": [{"x": x, "y": y} for x, y in self.loading_areas], + "unloading_areas": [{"x": x, "y": y} for x, y in self.unloading_areas], + "obstacles": [{"x": x, "y": y} for x, y in self.obstacles] + } + + with open(filepath, 'w') as f: + json.dump(config_data, f, indent=2) + + @classmethod + def load_from_file(cls, filepath: str) -> 'EnvironmentConfig': + """ + 浠庢枃浠跺姞杞界幆澧冮厤缃� + + Args: + filepath: 鏂囦欢璺緞 + + Returns: + EnvironmentConfig: 鐜閰嶇疆瀵硅薄 + """ + with open(filepath, 'r') as f: + config_data = json.load(f) + + env_config = cls(config_data["width"], config_data["height"]) + + # 鍔犺浇绔欑偣 + for pos_str, info in config_data["stations"].items(): + x, y = map(int, pos_str.split(',')) + load_x, load_y = info["load_position"] + unload_x, unload_y = info["unload_position"] + env_config.add_station(x, y, info["capacity"], + (load_x, load_y), (unload_x, unload_y)) + + # 鍔犺浇璺緞 + for path in config_data["paths"]: + env_config.add_path(path["x"], path["y"]) + + # 鍔犺浇浠撳偍鍖哄煙 + for area in config_data["storage_areas"]: + env_config.add_storage_area(area["x"], area["y"]) + + # 鍔犺浇闅滅鐗� + for obstacle in config_data["obstacles"]: + env_config.add_obstacle(obstacle["x"], obstacle["y"]) + + # 鏋勫缓閭绘帴琛� + env_config.build_adjacency_list() + + return env_config + + def create_default_environment(self) -> 'EnvironmentConfig': + """ + 鍒涘缓榛樿鐜閰嶇疆锛堟寜鐓ч渶姹傛弿杩伴厤缃珯鐐瑰拰璺緞锛� + + Returns: + EnvironmentConfig: 鐜閰嶇疆瀵硅薄 + """ + # 娓呯┖褰撳墠閰嶇疆 + self.grid = np.full((self.height, self.width), CellType.EMPTY) + self.stations = {} + self.paths = set() + self.storage_areas = set() + self.loading_areas = set() + self.unloading_areas = set() + self.obstacles = set() + + # 閰嶇疆20涓珯鐐� + station_y = self.height - 5 + station_spacing = self.width // 25 # 绔欑偣闂磋窛 + + for i in range(20): + station_x = (i + 1) * station_spacing + self.add_station(station_x, station_y) + + # 浠撳偍鍖哄煙 + storage_start_y = 5 + storage_end_y = station_y - 10 + + for col in range(5): + col_x = (col + 1) * (self.width // 6) + + for row_y in range(storage_start_y, storage_end_y, 5): + for dx in range(-1, 2): + for dy in range(-1, 2): + self.add_storage_area(col_x + dx, row_y + dy) + + # 閰嶇疆璺緞 + # 姘村钩涓昏矾寰� + for x in range(5, self.width - 5): + # 绔欑偣鍓嶆按骞宠矾寰� + self.add_path(x, station_y - 5) + # 璐ф灦鍖哄煙姘村钩璺緞 + for path_y in range(10, storage_end_y, 10): + self.add_path(x, path_y) + + # 鍨傜洿杩炴帴璺緞 + for col in range(7): + path_x = col * (self.width // 7) + for y in range(5, station_y): + self.add_path(path_x, y) + + # 鏋勫缓閭绘帴琛� + self.build_adjacency_list() + + return self + + +# 鍒涘缓鍜屽垵濮嬪寲榛樿鐜鐨勮緟鍔╁嚱鏁� +def create_default_environment(width=100, height=100) -> EnvironmentConfig: + """ + 鍒涘缓榛樿鐜閰嶇疆 + + Args: + width: 鐜瀹藉害 + height: 鐜楂樺害 + + Returns: + EnvironmentConfig: 榛樿鐜閰嶇疆 + """ + env_config = EnvironmentConfig(width, height) + return env_config.create_default_environment() \ No newline at end of file diff --git a/config/settings.py b/config/settings.py new file mode 100644 index 0000000..6911d2d --- /dev/null +++ b/config/settings.py @@ -0,0 +1,27 @@ +""" +鍏ㄥ眬璁剧疆鍜屽弬鏁伴厤缃� - 浠呬繚鐣欏疄闄呬娇鐢ㄧ殑閰嶇疆椤� +""" + +# RCS鏈嶅姟鍣ㄩ厤缃� +RCS_SERVER_HOST = "10.10.10.156" +RCS_SERVER_PORT = 8088 + +# 绠楁硶鏈嶅姟鍣ㄩ厤缃� +ALGORITHM_SERVER_HOST = "10.10.10.239" +ALGORITHM_SERVER_PORT = 8002 + +# 璺緞鐩戞帶鏈嶅姟閰嶇疆 +MONITOR_POLLING_INTERVAL = 5.0 # 璺緞鐩戞帶杞闂撮殧 + +# API閰嶇疆 +AGV_STATUS_API_ENDPOINT = "/api/open/algorithm/getAgv" # AGV鐘舵�佹煡璇rl + +# AGV閰嶇疆 +DEFAULT_AGV_COUNT = 50 # 榛樿AGV鏁伴噺 + +# 缃戠粶閰嶇疆 +REQUEST_TIMEOUT = 30 # HTTP璇锋眰瓒呮椂鏃堕棿锛堢锛� + +# 鏃ュ織閰嶇疆 +LOG_LEVEL = "INFO" +LOG_FILE = "warehouse_system.log" # 鏃ュ織鏂囦欢璺緞 \ No newline at end of file diff --git a/environment.json b/environment.json new file mode 100644 index 0000000..6915ebc --- /dev/null +++ b/environment.json @@ -0,0 +1,14960 @@ +{ + "width": 78, + "height": 50, + "stations": { + "2,48": { + "capacity": 4, + "load_position": [ + 3, + 48 + ], + "unload_position": [ + 1, + 48 + ] + }, + "5,48": { + "capacity": 4, + "load_position": [ + 6, + 48 + ], + "unload_position": [ + 4, + 48 + ] + }, + "8,48": { + "capacity": 4, + "load_position": [ + 9, + 48 + ], + "unload_position": [ + 7, + 48 + ] + }, + "12,48": { + "capacity": 4, + "load_position": [ + 13, + 48 + ], + "unload_position": [ + 11, + 48 + ] + }, + "18,48": { + "capacity": 4, + "load_position": [ + 19, + 48 + ], + "unload_position": [ + 17, + 48 + ] + }, + "21,48": { + "capacity": 4, + "load_position": [ + 22, + 48 + ], + "unload_position": [ + 20, + 48 + ] + }, + "24,48": { + "capacity": 4, + "load_position": [ + 25, + 48 + ], + "unload_position": [ + 23, + 48 + ] + }, + "28,48": { + "capacity": 4, + "load_position": [ + 29, + 48 + ], + "unload_position": [ + 27, + 48 + ] + }, + "34,48": { + "capacity": 4, + "load_position": [ + 35, + 48 + ], + "unload_position": [ + 33, + 48 + ] + }, + "37,48": { + "capacity": 4, + "load_position": [ + 38, + 48 + ], + "unload_position": [ + 36, + 48 + ] + }, + "40,48": { + "capacity": 4, + "load_position": [ + 41, + 48 + ], + "unload_position": [ + 39, + 48 + ] + }, + "44,48": { + "capacity": 4, + "load_position": [ + 45, + 48 + ], + "unload_position": [ + 43, + 48 + ] + }, + "50,48": { + "capacity": 4, + "load_position": [ + 51, + 48 + ], + "unload_position": [ + 49, + 48 + ] + }, + "53,48": { + "capacity": 4, + "load_position": [ + 54, + 48 + ], + "unload_position": [ + 52, + 48 + ] + }, + "56,48": { + "capacity": 4, + "load_position": [ + 57, + 48 + ], + "unload_position": [ + 55, + 48 + ] + }, + "60,48": { + "capacity": 4, + "load_position": [ + 61, + 48 + ], + "unload_position": [ + 59, + 48 + ] + }, + "66,48": { + "capacity": 4, + "load_position": [ + 67, + 48 + ], + "unload_position": [ + 65, + 48 + ] + }, + "69,48": { + "capacity": 4, + "load_position": [ + 70, + 48 + ], + "unload_position": [ + 68, + 48 + ] + }, + "72,48": { + "capacity": 4, + "load_position": [ + 73, + 48 + ], + "unload_position": [ + 71, + 48 + ] + }, + "76,48": { + "capacity": 4, + "load_position": [ + 77, + 48 + ], + "unload_position": [ + 75, + 48 + ] + } + }, + "paths": [ + { + "x": 17, + "y": 0 + }, + { + "x": 18, + "y": 0 + }, + { + "x": 19, + "y": 0 + }, + { + "x": 20, + "y": 0 + }, + { + "x": 21, + "y": 0 + }, + { + "x": 22, + "y": 0 + }, + { + "x": 23, + "y": 0 + }, + { + "x": 24, + "y": 0 + }, + { + "x": 25, + "y": 0 + }, + { + "x": 26, + "y": 0 + }, + { + "x": 27, + "y": 0 + }, + { + "x": 28, + "y": 0 + }, + { + "x": 29, + "y": 0 + }, + { + "x": 30, + "y": 0 + }, + { + "x": 31, + "y": 0 + }, + { + "x": 32, + "y": 0 + }, + { + "x": 33, + "y": 0 + }, + { + "x": 34, + "y": 0 + }, + { + "x": 35, + "y": 0 + }, + { + "x": 36, + "y": 0 + }, + { + "x": 37, + "y": 0 + }, + { + "x": 38, + "y": 0 + }, + { + "x": 39, + "y": 0 + }, + { + "x": 40, + "y": 0 + }, + { + "x": 41, + "y": 0 + }, + { + "x": 42, + "y": 0 + }, + { + "x": 43, + "y": 0 + }, + { + "x": 44, + "y": 0 + }, + { + "x": 45, + "y": 0 + }, + { + "x": 46, + "y": 0 + }, + { + "x": 47, + "y": 0 + }, + { + "x": 48, + "y": 0 + }, + { + "x": 49, + "y": 0 + }, + { + "x": 50, + "y": 0 + }, + { + "x": 51, + "y": 0 + }, + { + "x": 52, + "y": 0 + }, + { + "x": 53, + "y": 0 + }, + { + "x": 54, + "y": 0 + }, + { + "x": 55, + "y": 0 + }, + { + "x": 56, + "y": 0 + }, + { + "x": 57, + "y": 0 + }, + { + "x": 58, + "y": 0 + }, + { + "x": 59, + "y": 0 + }, + { + "x": 60, + "y": 0 + }, + { + "x": 61, + "y": 0 + }, + { + "x": 17, + "y": 1 + }, + { + "x": 19, + "y": 1 + }, + { + "x": 21, + "y": 1 + }, + { + "x": 23, + "y": 1 + }, + { + "x": 25, + "y": 1 + }, + { + "x": 27, + "y": 1 + }, + { + "x": 29, + "y": 1 + }, + { + "x": 31, + "y": 1 + }, + { + "x": 33, + "y": 1 + }, + { + "x": 35, + "y": 1 + }, + { + "x": 37, + "y": 1 + }, + { + "x": 39, + "y": 1 + }, + { + "x": 41, + "y": 1 + }, + { + "x": 43, + "y": 1 + }, + { + "x": 45, + "y": 1 + }, + { + "x": 47, + "y": 1 + }, + { + "x": 49, + "y": 1 + }, + { + "x": 51, + "y": 1 + }, + { + "x": 53, + "y": 1 + }, + { + "x": 55, + "y": 1 + }, + { + "x": 57, + "y": 1 + }, + { + "x": 59, + "y": 1 + }, + { + "x": 61, + "y": 1 + }, + { + "x": 17, + "y": 2 + }, + { + "x": 19, + "y": 2 + }, + { + "x": 21, + "y": 2 + }, + { + "x": 23, + "y": 2 + }, + { + "x": 25, + "y": 2 + }, + { + "x": 27, + "y": 2 + }, + { + "x": 29, + "y": 2 + }, + { + "x": 31, + "y": 2 + }, + { + "x": 33, + "y": 2 + }, + { + "x": 35, + "y": 2 + }, + { + "x": 37, + "y": 2 + }, + { + "x": 39, + "y": 2 + }, + { + "x": 41, + "y": 2 + }, + { + "x": 43, + "y": 2 + }, + { + "x": 45, + "y": 2 + }, + { + "x": 47, + "y": 2 + }, + { + "x": 49, + "y": 2 + }, + { + "x": 51, + "y": 2 + }, + { + "x": 53, + "y": 2 + }, + { + "x": 55, + "y": 2 + }, + { + "x": 57, + "y": 2 + }, + { + "x": 59, + "y": 2 + }, + { + "x": 61, + "y": 2 + }, + { + "x": 17, + "y": 3 + }, + { + "x": 19, + "y": 3 + }, + { + "x": 21, + "y": 3 + }, + { + "x": 23, + "y": 3 + }, + { + "x": 25, + "y": 3 + }, + { + "x": 27, + "y": 3 + }, + { + "x": 29, + "y": 3 + }, + { + "x": 31, + "y": 3 + }, + { + "x": 33, + "y": 3 + }, + { + "x": 35, + "y": 3 + }, + { + "x": 37, + "y": 3 + }, + { + "x": 39, + "y": 3 + }, + { + "x": 41, + "y": 3 + }, + { + "x": 43, + "y": 3 + }, + { + "x": 45, + "y": 3 + }, + { + "x": 47, + "y": 3 + }, + { + "x": 49, + "y": 3 + }, + { + "x": 51, + "y": 3 + }, + { + "x": 53, + "y": 3 + }, + { + "x": 55, + "y": 3 + }, + { + "x": 57, + "y": 3 + }, + { + "x": 59, + "y": 3 + }, + { + "x": 61, + "y": 3 + }, + { + "x": 17, + "y": 4 + }, + { + "x": 19, + "y": 4 + }, + { + "x": 21, + "y": 4 + }, + { + "x": 23, + "y": 4 + }, + { + "x": 25, + "y": 4 + }, + { + "x": 27, + "y": 4 + }, + { + "x": 29, + "y": 4 + }, + { + "x": 31, + "y": 4 + }, + { + "x": 33, + "y": 4 + }, + { + "x": 35, + "y": 4 + }, + { + "x": 37, + "y": 4 + }, + { + "x": 39, + "y": 4 + }, + { + "x": 41, + "y": 4 + }, + { + "x": 43, + "y": 4 + }, + { + "x": 45, + "y": 4 + }, + { + "x": 47, + "y": 4 + }, + { + "x": 49, + "y": 4 + }, + { + "x": 51, + "y": 4 + }, + { + "x": 53, + "y": 4 + }, + { + "x": 55, + "y": 4 + }, + { + "x": 57, + "y": 4 + }, + { + "x": 59, + "y": 4 + }, + { + "x": 61, + "y": 4 + }, + { + "x": 17, + "y": 5 + }, + { + "x": 19, + "y": 5 + }, + { + "x": 21, + "y": 5 + }, + { + "x": 23, + "y": 5 + }, + { + "x": 25, + "y": 5 + }, + { + "x": 27, + "y": 5 + }, + { + "x": 29, + "y": 5 + }, + { + "x": 31, + "y": 5 + }, + { + "x": 33, + "y": 5 + }, + { + "x": 35, + "y": 5 + }, + { + "x": 37, + "y": 5 + }, + { + "x": 39, + "y": 5 + }, + { + "x": 41, + "y": 5 + }, + { + "x": 43, + "y": 5 + }, + { + "x": 45, + "y": 5 + }, + { + "x": 47, + "y": 5 + }, + { + "x": 49, + "y": 5 + }, + { + "x": 51, + "y": 5 + }, + { + "x": 53, + "y": 5 + }, + { + "x": 55, + "y": 5 + }, + { + "x": 57, + "y": 5 + }, + { + "x": 59, + "y": 5 + }, + { + "x": 61, + "y": 5 + }, + { + "x": 17, + "y": 6 + }, + { + "x": 18, + "y": 6 + }, + { + "x": 19, + "y": 6 + }, + { + "x": 20, + "y": 6 + }, + { + "x": 21, + "y": 6 + }, + { + "x": 22, + "y": 6 + }, + { + "x": 23, + "y": 6 + }, + { + "x": 24, + "y": 6 + }, + { + "x": 25, + "y": 6 + }, + { + "x": 26, + "y": 6 + }, + { + "x": 27, + "y": 6 + }, + { + "x": 28, + "y": 6 + }, + { + "x": 29, + "y": 6 + }, + { + "x": 30, + "y": 6 + }, + { + "x": 31, + "y": 6 + }, + { + "x": 32, + "y": 6 + }, + { + "x": 33, + "y": 6 + }, + { + "x": 34, + "y": 6 + }, + { + "x": 35, + "y": 6 + }, + { + "x": 36, + "y": 6 + }, + { + "x": 37, + "y": 6 + }, + { + "x": 38, + "y": 6 + }, + { + "x": 39, + "y": 6 + }, + { + "x": 40, + "y": 6 + }, + { + "x": 41, + "y": 6 + }, + { + "x": 42, + "y": 6 + }, + { + "x": 43, + "y": 6 + }, + { + "x": 44, + "y": 6 + }, + { + "x": 45, + "y": 6 + }, + { + "x": 46, + "y": 6 + }, + { + "x": 47, + "y": 6 + }, + { + "x": 48, + "y": 6 + }, + { + "x": 49, + "y": 6 + }, + { + "x": 50, + "y": 6 + }, + { + "x": 51, + "y": 6 + }, + { + "x": 52, + "y": 6 + }, + { + "x": 53, + "y": 6 + }, + { + "x": 54, + "y": 6 + }, + { + "x": 55, + "y": 6 + }, + { + "x": 56, + "y": 6 + }, + { + "x": 57, + "y": 6 + }, + { + "x": 58, + "y": 6 + }, + { + "x": 59, + "y": 6 + }, + { + "x": 60, + "y": 6 + }, + { + "x": 61, + "y": 6 + }, + { + "x": 17, + "y": 7 + }, + { + "x": 19, + "y": 7 + }, + { + "x": 21, + "y": 7 + }, + { + "x": 23, + "y": 7 + }, + { + "x": 25, + "y": 7 + }, + { + "x": 27, + "y": 7 + }, + { + "x": 29, + "y": 7 + }, + { + "x": 31, + "y": 7 + }, + { + "x": 33, + "y": 7 + }, + { + "x": 35, + "y": 7 + }, + { + "x": 37, + "y": 7 + }, + { + "x": 39, + "y": 7 + }, + { + "x": 41, + "y": 7 + }, + { + "x": 43, + "y": 7 + }, + { + "x": 45, + "y": 7 + }, + { + "x": 47, + "y": 7 + }, + { + "x": 49, + "y": 7 + }, + { + "x": 51, + "y": 7 + }, + { + "x": 53, + "y": 7 + }, + { + "x": 55, + "y": 7 + }, + { + "x": 57, + "y": 7 + }, + { + "x": 59, + "y": 7 + }, + { + "x": 61, + "y": 7 + }, + { + "x": 17, + "y": 8 + }, + { + "x": 19, + "y": 8 + }, + { + "x": 21, + "y": 8 + }, + { + "x": 23, + "y": 8 + }, + { + "x": 25, + "y": 8 + }, + { + "x": 27, + "y": 8 + }, + { + "x": 29, + "y": 8 + }, + { + "x": 31, + "y": 8 + }, + { + "x": 33, + "y": 8 + }, + { + "x": 35, + "y": 8 + }, + { + "x": 37, + "y": 8 + }, + { + "x": 39, + "y": 8 + }, + { + "x": 41, + "y": 8 + }, + { + "x": 43, + "y": 8 + }, + { + "x": 45, + "y": 8 + }, + { + "x": 47, + "y": 8 + }, + { + "x": 49, + "y": 8 + }, + { + "x": 51, + "y": 8 + }, + { + "x": 53, + "y": 8 + }, + { + "x": 55, + "y": 8 + }, + { + "x": 57, + "y": 8 + }, + { + "x": 59, + "y": 8 + }, + { + "x": 61, + "y": 8 + }, + { + "x": 17, + "y": 9 + }, + { + "x": 19, + "y": 9 + }, + { + "x": 21, + "y": 9 + }, + { + "x": 23, + "y": 9 + }, + { + "x": 25, + "y": 9 + }, + { + "x": 27, + "y": 9 + }, + { + "x": 29, + "y": 9 + }, + { + "x": 31, + "y": 9 + }, + { + "x": 33, + "y": 9 + }, + { + "x": 35, + "y": 9 + }, + { + "x": 37, + "y": 9 + }, + { + "x": 39, + "y": 9 + }, + { + "x": 41, + "y": 9 + }, + { + "x": 43, + "y": 9 + }, + { + "x": 45, + "y": 9 + }, + { + "x": 47, + "y": 9 + }, + { + "x": 49, + "y": 9 + }, + { + "x": 51, + "y": 9 + }, + { + "x": 53, + "y": 9 + }, + { + "x": 55, + "y": 9 + }, + { + "x": 57, + "y": 9 + }, + { + "x": 59, + "y": 9 + }, + { + "x": 61, + "y": 9 + }, + { + "x": 17, + "y": 10 + }, + { + "x": 19, + "y": 10 + }, + { + "x": 21, + "y": 10 + }, + { + "x": 23, + "y": 10 + }, + { + "x": 25, + "y": 10 + }, + { + "x": 27, + "y": 10 + }, + { + "x": 29, + "y": 10 + }, + { + "x": 31, + "y": 10 + }, + { + "x": 33, + "y": 10 + }, + { + "x": 35, + "y": 10 + }, + { + "x": 37, + "y": 10 + }, + { + "x": 39, + "y": 10 + }, + { + "x": 41, + "y": 10 + }, + { + "x": 43, + "y": 10 + }, + { + "x": 45, + "y": 10 + }, + { + "x": 47, + "y": 10 + }, + { + "x": 49, + "y": 10 + }, + { + "x": 51, + "y": 10 + }, + { + "x": 53, + "y": 10 + }, + { + "x": 55, + "y": 10 + }, + { + "x": 57, + "y": 10 + }, + { + "x": 59, + "y": 10 + }, + { + "x": 61, + "y": 10 + }, + { + "x": 17, + "y": 11 + }, + { + "x": 19, + "y": 11 + }, + { + "x": 21, + "y": 11 + }, + { + "x": 23, + "y": 11 + }, + { + "x": 25, + "y": 11 + }, + { + "x": 27, + "y": 11 + }, + { + "x": 29, + "y": 11 + }, + { + "x": 31, + "y": 11 + }, + { + "x": 33, + "y": 11 + }, + { + "x": 35, + "y": 11 + }, + { + "x": 37, + "y": 11 + }, + { + "x": 39, + "y": 11 + }, + { + "x": 41, + "y": 11 + }, + { + "x": 43, + "y": 11 + }, + { + "x": 45, + "y": 11 + }, + { + "x": 47, + "y": 11 + }, + { + "x": 49, + "y": 11 + }, + { + "x": 51, + "y": 11 + }, + { + "x": 53, + "y": 11 + }, + { + "x": 55, + "y": 11 + }, + { + "x": 57, + "y": 11 + }, + { + "x": 59, + "y": 11 + }, + { + "x": 61, + "y": 11 + }, + { + "x": 1, + "y": 12 + }, + { + "x": 2, + "y": 12 + }, + { + "x": 3, + "y": 12 + }, + { + "x": 4, + "y": 12 + }, + { + "x": 5, + "y": 12 + }, + { + "x": 6, + "y": 12 + }, + { + "x": 7, + "y": 12 + }, + { + "x": 8, + "y": 12 + }, + { + "x": 9, + "y": 12 + }, + { + "x": 10, + "y": 12 + }, + { + "x": 11, + "y": 12 + }, + { + "x": 12, + "y": 12 + }, + { + "x": 13, + "y": 12 + }, + { + "x": 14, + "y": 12 + }, + { + "x": 15, + "y": 12 + }, + { + "x": 16, + "y": 12 + }, + { + "x": 17, + "y": 12 + }, + { + "x": 18, + "y": 12 + }, + { + "x": 19, + "y": 12 + }, + { + "x": 20, + "y": 12 + }, + { + "x": 21, + "y": 12 + }, + { + "x": 22, + "y": 12 + }, + { + "x": 23, + "y": 12 + }, + { + "x": 24, + "y": 12 + }, + { + "x": 25, + "y": 12 + }, + { + "x": 26, + "y": 12 + }, + { + "x": 27, + "y": 12 + }, + { + "x": 28, + "y": 12 + }, + { + "x": 29, + "y": 12 + }, + { + "x": 30, + "y": 12 + }, + { + "x": 31, + "y": 12 + }, + { + "x": 32, + "y": 12 + }, + { + "x": 33, + "y": 12 + }, + { + "x": 34, + "y": 12 + }, + { + "x": 35, + "y": 12 + }, + { + "x": 36, + "y": 12 + }, + { + "x": 37, + "y": 12 + }, + { + "x": 38, + "y": 12 + }, + { + "x": 39, + "y": 12 + }, + { + "x": 40, + "y": 12 + }, + { + "x": 41, + "y": 12 + }, + { + "x": 42, + "y": 12 + }, + { + "x": 43, + "y": 12 + }, + { + "x": 44, + "y": 12 + }, + { + "x": 45, + "y": 12 + }, + { + "x": 46, + "y": 12 + }, + { + "x": 47, + "y": 12 + }, + { + "x": 48, + "y": 12 + }, + { + "x": 49, + "y": 12 + }, + { + "x": 50, + "y": 12 + }, + { + "x": 51, + "y": 12 + }, + { + "x": 52, + "y": 12 + }, + { + "x": 53, + "y": 12 + }, + { + "x": 54, + "y": 12 + }, + { + "x": 55, + "y": 12 + }, + { + "x": 56, + "y": 12 + }, + { + "x": 57, + "y": 12 + }, + { + "x": 58, + "y": 12 + }, + { + "x": 59, + "y": 12 + }, + { + "x": 60, + "y": 12 + }, + { + "x": 61, + "y": 12 + }, + { + "x": 62, + "y": 12 + }, + { + "x": 63, + "y": 12 + }, + { + "x": 64, + "y": 12 + }, + { + "x": 65, + "y": 12 + }, + { + "x": 66, + "y": 12 + }, + { + "x": 67, + "y": 12 + }, + { + "x": 68, + "y": 12 + }, + { + "x": 69, + "y": 12 + }, + { + "x": 70, + "y": 12 + }, + { + "x": 71, + "y": 12 + }, + { + "x": 72, + "y": 12 + }, + { + "x": 73, + "y": 12 + }, + { + "x": 74, + "y": 12 + }, + { + "x": 75, + "y": 12 + }, + { + "x": 1, + "y": 13 + }, + { + "x": 3, + "y": 13 + }, + { + "x": 5, + "y": 13 + }, + { + "x": 7, + "y": 13 + }, + { + "x": 9, + "y": 13 + }, + { + "x": 11, + "y": 13 + }, + { + "x": 13, + "y": 13 + }, + { + "x": 15, + "y": 13 + }, + { + "x": 17, + "y": 13 + }, + { + "x": 19, + "y": 13 + }, + { + "x": 21, + "y": 13 + }, + { + "x": 23, + "y": 13 + }, + { + "x": 25, + "y": 13 + }, + { + "x": 27, + "y": 13 + }, + { + "x": 29, + "y": 13 + }, + { + "x": 31, + "y": 13 + }, + { + "x": 33, + "y": 13 + }, + { + "x": 35, + "y": 13 + }, + { + "x": 37, + "y": 13 + }, + { + "x": 39, + "y": 13 + }, + { + "x": 41, + "y": 13 + }, + { + "x": 43, + "y": 13 + }, + { + "x": 45, + "y": 13 + }, + { + "x": 47, + "y": 13 + }, + { + "x": 49, + "y": 13 + }, + { + "x": 51, + "y": 13 + }, + { + "x": 53, + "y": 13 + }, + { + "x": 55, + "y": 13 + }, + { + "x": 57, + "y": 13 + }, + { + "x": 59, + "y": 13 + }, + { + "x": 61, + "y": 13 + }, + { + "x": 63, + "y": 13 + }, + { + "x": 65, + "y": 13 + }, + { + "x": 67, + "y": 13 + }, + { + "x": 69, + "y": 13 + }, + { + "x": 71, + "y": 13 + }, + { + "x": 73, + "y": 13 + }, + { + "x": 75, + "y": 13 + }, + { + "x": 1, + "y": 14 + }, + { + "x": 3, + "y": 14 + }, + { + "x": 5, + "y": 14 + }, + { + "x": 7, + "y": 14 + }, + { + "x": 9, + "y": 14 + }, + { + "x": 11, + "y": 14 + }, + { + "x": 13, + "y": 14 + }, + { + "x": 15, + "y": 14 + }, + { + "x": 17, + "y": 14 + }, + { + "x": 19, + "y": 14 + }, + { + "x": 21, + "y": 14 + }, + { + "x": 23, + "y": 14 + }, + { + "x": 25, + "y": 14 + }, + { + "x": 27, + "y": 14 + }, + { + "x": 29, + "y": 14 + }, + { + "x": 31, + "y": 14 + }, + { + "x": 33, + "y": 14 + }, + { + "x": 35, + "y": 14 + }, + { + "x": 37, + "y": 14 + }, + { + "x": 39, + "y": 14 + }, + { + "x": 41, + "y": 14 + }, + { + "x": 43, + "y": 14 + }, + { + "x": 45, + "y": 14 + }, + { + "x": 47, + "y": 14 + }, + { + "x": 49, + "y": 14 + }, + { + "x": 51, + "y": 14 + }, + { + "x": 53, + "y": 14 + }, + { + "x": 55, + "y": 14 + }, + { + "x": 57, + "y": 14 + }, + { + "x": 59, + "y": 14 + }, + { + "x": 61, + "y": 14 + }, + { + "x": 63, + "y": 14 + }, + { + "x": 65, + "y": 14 + }, + { + "x": 67, + "y": 14 + }, + { + "x": 69, + "y": 14 + }, + { + "x": 71, + "y": 14 + }, + { + "x": 73, + "y": 14 + }, + { + "x": 75, + "y": 14 + }, + { + "x": 1, + "y": 15 + }, + { + "x": 3, + "y": 15 + }, + { + "x": 5, + "y": 15 + }, + { + "x": 7, + "y": 15 + }, + { + "x": 9, + "y": 15 + }, + { + "x": 11, + "y": 15 + }, + { + "x": 13, + "y": 15 + }, + { + "x": 15, + "y": 15 + }, + { + "x": 17, + "y": 15 + }, + { + "x": 19, + "y": 15 + }, + { + "x": 21, + "y": 15 + }, + { + "x": 23, + "y": 15 + }, + { + "x": 25, + "y": 15 + }, + { + "x": 27, + "y": 15 + }, + { + "x": 29, + "y": 15 + }, + { + "x": 31, + "y": 15 + }, + { + "x": 33, + "y": 15 + }, + { + "x": 35, + "y": 15 + }, + { + "x": 37, + "y": 15 + }, + { + "x": 39, + "y": 15 + }, + { + "x": 41, + "y": 15 + }, + { + "x": 43, + "y": 15 + }, + { + "x": 45, + "y": 15 + }, + { + "x": 47, + "y": 15 + }, + { + "x": 49, + "y": 15 + }, + { + "x": 51, + "y": 15 + }, + { + "x": 53, + "y": 15 + }, + { + "x": 55, + "y": 15 + }, + { + "x": 57, + "y": 15 + }, + { + "x": 59, + "y": 15 + }, + { + "x": 61, + "y": 15 + }, + { + "x": 63, + "y": 15 + }, + { + "x": 65, + "y": 15 + }, + { + "x": 67, + "y": 15 + }, + { + "x": 69, + "y": 15 + }, + { + "x": 71, + "y": 15 + }, + { + "x": 73, + "y": 15 + }, + { + "x": 75, + "y": 15 + }, + { + "x": 1, + "y": 16 + }, + { + "x": 3, + "y": 16 + }, + { + "x": 5, + "y": 16 + }, + { + "x": 7, + "y": 16 + }, + { + "x": 9, + "y": 16 + }, + { + "x": 11, + "y": 16 + }, + { + "x": 13, + "y": 16 + }, + { + "x": 15, + "y": 16 + }, + { + "x": 17, + "y": 16 + }, + { + "x": 19, + "y": 16 + }, + { + "x": 21, + "y": 16 + }, + { + "x": 23, + "y": 16 + }, + { + "x": 25, + "y": 16 + }, + { + "x": 27, + "y": 16 + }, + { + "x": 29, + "y": 16 + }, + { + "x": 31, + "y": 16 + }, + { + "x": 33, + "y": 16 + }, + { + "x": 35, + "y": 16 + }, + { + "x": 37, + "y": 16 + }, + { + "x": 39, + "y": 16 + }, + { + "x": 41, + "y": 16 + }, + { + "x": 43, + "y": 16 + }, + { + "x": 45, + "y": 16 + }, + { + "x": 47, + "y": 16 + }, + { + "x": 49, + "y": 16 + }, + { + "x": 51, + "y": 16 + }, + { + "x": 53, + "y": 16 + }, + { + "x": 55, + "y": 16 + }, + { + "x": 57, + "y": 16 + }, + { + "x": 59, + "y": 16 + }, + { + "x": 61, + "y": 16 + }, + { + "x": 63, + "y": 16 + }, + { + "x": 65, + "y": 16 + }, + { + "x": 67, + "y": 16 + }, + { + "x": 69, + "y": 16 + }, + { + "x": 71, + "y": 16 + }, + { + "x": 73, + "y": 16 + }, + { + "x": 75, + "y": 16 + }, + { + "x": 1, + "y": 17 + }, + { + "x": 3, + "y": 17 + }, + { + "x": 5, + "y": 17 + }, + { + "x": 7, + "y": 17 + }, + { + "x": 9, + "y": 17 + }, + { + "x": 11, + "y": 17 + }, + { + "x": 13, + "y": 17 + }, + { + "x": 15, + "y": 17 + }, + { + "x": 17, + "y": 17 + }, + { + "x": 19, + "y": 17 + }, + { + "x": 21, + "y": 17 + }, + { + "x": 23, + "y": 17 + }, + { + "x": 25, + "y": 17 + }, + { + "x": 27, + "y": 17 + }, + { + "x": 29, + "y": 17 + }, + { + "x": 31, + "y": 17 + }, + { + "x": 33, + "y": 17 + }, + { + "x": 35, + "y": 17 + }, + { + "x": 37, + "y": 17 + }, + { + "x": 39, + "y": 17 + }, + { + "x": 41, + "y": 17 + }, + { + "x": 43, + "y": 17 + }, + { + "x": 45, + "y": 17 + }, + { + "x": 47, + "y": 17 + }, + { + "x": 49, + "y": 17 + }, + { + "x": 51, + "y": 17 + }, + { + "x": 53, + "y": 17 + }, + { + "x": 55, + "y": 17 + }, + { + "x": 57, + "y": 17 + }, + { + "x": 59, + "y": 17 + }, + { + "x": 61, + "y": 17 + }, + { + "x": 63, + "y": 17 + }, + { + "x": 65, + "y": 17 + }, + { + "x": 67, + "y": 17 + }, + { + "x": 69, + "y": 17 + }, + { + "x": 71, + "y": 17 + }, + { + "x": 73, + "y": 17 + }, + { + "x": 75, + "y": 17 + }, + { + "x": 1, + "y": 18 + }, + { + "x": 3, + "y": 18 + }, + { + "x": 5, + "y": 18 + }, + { + "x": 7, + "y": 18 + }, + { + "x": 9, + "y": 18 + }, + { + "x": 11, + "y": 18 + }, + { + "x": 13, + "y": 18 + }, + { + "x": 15, + "y": 18 + }, + { + "x": 17, + "y": 18 + }, + { + "x": 19, + "y": 18 + }, + { + "x": 21, + "y": 18 + }, + { + "x": 23, + "y": 18 + }, + { + "x": 25, + "y": 18 + }, + { + "x": 27, + "y": 18 + }, + { + "x": 29, + "y": 18 + }, + { + "x": 31, + "y": 18 + }, + { + "x": 33, + "y": 18 + }, + { + "x": 35, + "y": 18 + }, + { + "x": 37, + "y": 18 + }, + { + "x": 39, + "y": 18 + }, + { + "x": 41, + "y": 18 + }, + { + "x": 43, + "y": 18 + }, + { + "x": 45, + "y": 18 + }, + { + "x": 47, + "y": 18 + }, + { + "x": 49, + "y": 18 + }, + { + "x": 51, + "y": 18 + }, + { + "x": 53, + "y": 18 + }, + { + "x": 55, + "y": 18 + }, + { + "x": 57, + "y": 18 + }, + { + "x": 59, + "y": 18 + }, + { + "x": 61, + "y": 18 + }, + { + "x": 63, + "y": 18 + }, + { + "x": 65, + "y": 18 + }, + { + "x": 67, + "y": 18 + }, + { + "x": 69, + "y": 18 + }, + { + "x": 71, + "y": 18 + }, + { + "x": 73, + "y": 18 + }, + { + "x": 75, + "y": 18 + }, + { + "x": 1, + "y": 19 + }, + { + "x": 3, + "y": 19 + }, + { + "x": 5, + "y": 19 + }, + { + "x": 7, + "y": 19 + }, + { + "x": 9, + "y": 19 + }, + { + "x": 11, + "y": 19 + }, + { + "x": 13, + "y": 19 + }, + { + "x": 15, + "y": 19 + }, + { + "x": 17, + "y": 19 + }, + { + "x": 19, + "y": 19 + }, + { + "x": 21, + "y": 19 + }, + { + "x": 23, + "y": 19 + }, + { + "x": 25, + "y": 19 + }, + { + "x": 27, + "y": 19 + }, + { + "x": 29, + "y": 19 + }, + { + "x": 31, + "y": 19 + }, + { + "x": 33, + "y": 19 + }, + { + "x": 35, + "y": 19 + }, + { + "x": 37, + "y": 19 + }, + { + "x": 39, + "y": 19 + }, + { + "x": 41, + "y": 19 + }, + { + "x": 43, + "y": 19 + }, + { + "x": 45, + "y": 19 + }, + { + "x": 47, + "y": 19 + }, + { + "x": 49, + "y": 19 + }, + { + "x": 51, + "y": 19 + }, + { + "x": 53, + "y": 19 + }, + { + "x": 55, + "y": 19 + }, + { + "x": 57, + "y": 19 + }, + { + "x": 59, + "y": 19 + }, + { + "x": 61, + "y": 19 + }, + { + "x": 63, + "y": 19 + }, + { + "x": 65, + "y": 19 + }, + { + "x": 67, + "y": 19 + }, + { + "x": 69, + "y": 19 + }, + { + "x": 71, + "y": 19 + }, + { + "x": 73, + "y": 19 + }, + { + "x": 75, + "y": 19 + }, + { + "x": 1, + "y": 20 + }, + { + "x": 3, + "y": 20 + }, + { + "x": 5, + "y": 20 + }, + { + "x": 7, + "y": 20 + }, + { + "x": 9, + "y": 20 + }, + { + "x": 11, + "y": 20 + }, + { + "x": 13, + "y": 20 + }, + { + "x": 15, + "y": 20 + }, + { + "x": 17, + "y": 20 + }, + { + "x": 19, + "y": 20 + }, + { + "x": 21, + "y": 20 + }, + { + "x": 23, + "y": 20 + }, + { + "x": 25, + "y": 20 + }, + { + "x": 27, + "y": 20 + }, + { + "x": 29, + "y": 20 + }, + { + "x": 31, + "y": 20 + }, + { + "x": 33, + "y": 20 + }, + { + "x": 35, + "y": 20 + }, + { + "x": 37, + "y": 20 + }, + { + "x": 39, + "y": 20 + }, + { + "x": 41, + "y": 20 + }, + { + "x": 43, + "y": 20 + }, + { + "x": 45, + "y": 20 + }, + { + "x": 47, + "y": 20 + }, + { + "x": 49, + "y": 20 + }, + { + "x": 51, + "y": 20 + }, + { + "x": 53, + "y": 20 + }, + { + "x": 55, + "y": 20 + }, + { + "x": 57, + "y": 20 + }, + { + "x": 59, + "y": 20 + }, + { + "x": 61, + "y": 20 + }, + { + "x": 63, + "y": 20 + }, + { + "x": 65, + "y": 20 + }, + { + "x": 67, + "y": 20 + }, + { + "x": 69, + "y": 20 + }, + { + "x": 71, + "y": 20 + }, + { + "x": 73, + "y": 20 + }, + { + "x": 75, + "y": 20 + }, + { + "x": 1, + "y": 21 + }, + { + "x": 3, + "y": 21 + }, + { + "x": 5, + "y": 21 + }, + { + "x": 7, + "y": 21 + }, + { + "x": 9, + "y": 21 + }, + { + "x": 11, + "y": 21 + }, + { + "x": 13, + "y": 21 + }, + { + "x": 15, + "y": 21 + }, + { + "x": 17, + "y": 21 + }, + { + "x": 19, + "y": 21 + }, + { + "x": 21, + "y": 21 + }, + { + "x": 23, + "y": 21 + }, + { + "x": 25, + "y": 21 + }, + { + "x": 27, + "y": 21 + }, + { + "x": 29, + "y": 21 + }, + { + "x": 31, + "y": 21 + }, + { + "x": 33, + "y": 21 + }, + { + "x": 35, + "y": 21 + }, + { + "x": 37, + "y": 21 + }, + { + "x": 39, + "y": 21 + }, + { + "x": 41, + "y": 21 + }, + { + "x": 43, + "y": 21 + }, + { + "x": 45, + "y": 21 + }, + { + "x": 47, + "y": 21 + }, + { + "x": 49, + "y": 21 + }, + { + "x": 51, + "y": 21 + }, + { + "x": 53, + "y": 21 + }, + { + "x": 55, + "y": 21 + }, + { + "x": 57, + "y": 21 + }, + { + "x": 59, + "y": 21 + }, + { + "x": 61, + "y": 21 + }, + { + "x": 63, + "y": 21 + }, + { + "x": 65, + "y": 21 + }, + { + "x": 67, + "y": 21 + }, + { + "x": 69, + "y": 21 + }, + { + "x": 71, + "y": 21 + }, + { + "x": 73, + "y": 21 + }, + { + "x": 75, + "y": 21 + }, + { + "x": 1, + "y": 22 + }, + { + "x": 2, + "y": 22 + }, + { + "x": 3, + "y": 22 + }, + { + "x": 4, + "y": 22 + }, + { + "x": 5, + "y": 22 + }, + { + "x": 6, + "y": 22 + }, + { + "x": 7, + "y": 22 + }, + { + "x": 8, + "y": 22 + }, + { + "x": 9, + "y": 22 + }, + { + "x": 10, + "y": 22 + }, + { + "x": 11, + "y": 22 + }, + { + "x": 12, + "y": 22 + }, + { + "x": 13, + "y": 22 + }, + { + "x": 14, + "y": 22 + }, + { + "x": 15, + "y": 22 + }, + { + "x": 16, + "y": 22 + }, + { + "x": 17, + "y": 22 + }, + { + "x": 18, + "y": 22 + }, + { + "x": 19, + "y": 22 + }, + { + "x": 20, + "y": 22 + }, + { + "x": 21, + "y": 22 + }, + { + "x": 22, + "y": 22 + }, + { + "x": 23, + "y": 22 + }, + { + "x": 24, + "y": 22 + }, + { + "x": 25, + "y": 22 + }, + { + "x": 26, + "y": 22 + }, + { + "x": 27, + "y": 22 + }, + { + "x": 28, + "y": 22 + }, + { + "x": 29, + "y": 22 + }, + { + "x": 30, + "y": 22 + }, + { + "x": 31, + "y": 22 + }, + { + "x": 32, + "y": 22 + }, + { + "x": 33, + "y": 22 + }, + { + "x": 34, + "y": 22 + }, + { + "x": 35, + "y": 22 + }, + { + "x": 36, + "y": 22 + }, + { + "x": 37, + "y": 22 + }, + { + "x": 38, + "y": 22 + }, + { + "x": 39, + "y": 22 + }, + { + "x": 40, + "y": 22 + }, + { + "x": 41, + "y": 22 + }, + { + "x": 42, + "y": 22 + }, + { + "x": 43, + "y": 22 + }, + { + "x": 44, + "y": 22 + }, + { + "x": 45, + "y": 22 + }, + { + "x": 46, + "y": 22 + }, + { + "x": 47, + "y": 22 + }, + { + "x": 48, + "y": 22 + }, + { + "x": 49, + "y": 22 + }, + { + "x": 50, + "y": 22 + }, + { + "x": 51, + "y": 22 + }, + { + "x": 52, + "y": 22 + }, + { + "x": 53, + "y": 22 + }, + { + "x": 54, + "y": 22 + }, + { + "x": 55, + "y": 22 + }, + { + "x": 56, + "y": 22 + }, + { + "x": 57, + "y": 22 + }, + { + "x": 58, + "y": 22 + }, + { + "x": 59, + "y": 22 + }, + { + "x": 60, + "y": 22 + }, + { + "x": 61, + "y": 22 + }, + { + "x": 62, + "y": 22 + }, + { + "x": 63, + "y": 22 + }, + { + "x": 64, + "y": 22 + }, + { + "x": 65, + "y": 22 + }, + { + "x": 66, + "y": 22 + }, + { + "x": 67, + "y": 22 + }, + { + "x": 68, + "y": 22 + }, + { + "x": 69, + "y": 22 + }, + { + "x": 70, + "y": 22 + }, + { + "x": 71, + "y": 22 + }, + { + "x": 72, + "y": 22 + }, + { + "x": 73, + "y": 22 + }, + { + "x": 74, + "y": 22 + }, + { + "x": 75, + "y": 22 + }, + { + "x": 1, + "y": 23 + }, + { + "x": 3, + "y": 23 + }, + { + "x": 5, + "y": 23 + }, + { + "x": 9, + "y": 23 + }, + { + "x": 11, + "y": 23 + }, + { + "x": 13, + "y": 23 + }, + { + "x": 17, + "y": 23 + }, + { + "x": 19, + "y": 23 + }, + { + "x": 21, + "y": 23 + }, + { + "x": 25, + "y": 23 + }, + { + "x": 27, + "y": 23 + }, + { + "x": 29, + "y": 23 + }, + { + "x": 33, + "y": 23 + }, + { + "x": 35, + "y": 23 + }, + { + "x": 37, + "y": 23 + }, + { + "x": 41, + "y": 23 + }, + { + "x": 43, + "y": 23 + }, + { + "x": 45, + "y": 23 + }, + { + "x": 49, + "y": 23 + }, + { + "x": 51, + "y": 23 + }, + { + "x": 53, + "y": 23 + }, + { + "x": 57, + "y": 23 + }, + { + "x": 59, + "y": 23 + }, + { + "x": 61, + "y": 23 + }, + { + "x": 65, + "y": 23 + }, + { + "x": 67, + "y": 23 + }, + { + "x": 69, + "y": 23 + }, + { + "x": 73, + "y": 23 + }, + { + "x": 75, + "y": 23 + }, + { + "x": 1, + "y": 24 + }, + { + "x": 3, + "y": 24 + }, + { + "x": 5, + "y": 24 + }, + { + "x": 9, + "y": 24 + }, + { + "x": 11, + "y": 24 + }, + { + "x": 13, + "y": 24 + }, + { + "x": 17, + "y": 24 + }, + { + "x": 19, + "y": 24 + }, + { + "x": 21, + "y": 24 + }, + { + "x": 25, + "y": 24 + }, + { + "x": 27, + "y": 24 + }, + { + "x": 29, + "y": 24 + }, + { + "x": 33, + "y": 24 + }, + { + "x": 35, + "y": 24 + }, + { + "x": 37, + "y": 24 + }, + { + "x": 41, + "y": 24 + }, + { + "x": 43, + "y": 24 + }, + { + "x": 45, + "y": 24 + }, + { + "x": 49, + "y": 24 + }, + { + "x": 51, + "y": 24 + }, + { + "x": 53, + "y": 24 + }, + { + "x": 57, + "y": 24 + }, + { + "x": 59, + "y": 24 + }, + { + "x": 61, + "y": 24 + }, + { + "x": 65, + "y": 24 + }, + { + "x": 67, + "y": 24 + }, + { + "x": 69, + "y": 24 + }, + { + "x": 73, + "y": 24 + }, + { + "x": 75, + "y": 24 + }, + { + "x": 1, + "y": 25 + }, + { + "x": 3, + "y": 25 + }, + { + "x": 5, + "y": 25 + }, + { + "x": 9, + "y": 25 + }, + { + "x": 11, + "y": 25 + }, + { + "x": 13, + "y": 25 + }, + { + "x": 17, + "y": 25 + }, + { + "x": 19, + "y": 25 + }, + { + "x": 21, + "y": 25 + }, + { + "x": 25, + "y": 25 + }, + { + "x": 27, + "y": 25 + }, + { + "x": 29, + "y": 25 + }, + { + "x": 33, + "y": 25 + }, + { + "x": 35, + "y": 25 + }, + { + "x": 37, + "y": 25 + }, + { + "x": 41, + "y": 25 + }, + { + "x": 43, + "y": 25 + }, + { + "x": 45, + "y": 25 + }, + { + "x": 49, + "y": 25 + }, + { + "x": 51, + "y": 25 + }, + { + "x": 53, + "y": 25 + }, + { + "x": 57, + "y": 25 + }, + { + "x": 59, + "y": 25 + }, + { + "x": 61, + "y": 25 + }, + { + "x": 65, + "y": 25 + }, + { + "x": 67, + "y": 25 + }, + { + "x": 69, + "y": 25 + }, + { + "x": 73, + "y": 25 + }, + { + "x": 75, + "y": 25 + }, + { + "x": 1, + "y": 26 + }, + { + "x": 3, + "y": 26 + }, + { + "x": 5, + "y": 26 + }, + { + "x": 9, + "y": 26 + }, + { + "x": 11, + "y": 26 + }, + { + "x": 13, + "y": 26 + }, + { + "x": 17, + "y": 26 + }, + { + "x": 19, + "y": 26 + }, + { + "x": 21, + "y": 26 + }, + { + "x": 25, + "y": 26 + }, + { + "x": 27, + "y": 26 + }, + { + "x": 29, + "y": 26 + }, + { + "x": 33, + "y": 26 + }, + { + "x": 35, + "y": 26 + }, + { + "x": 37, + "y": 26 + }, + { + "x": 41, + "y": 26 + }, + { + "x": 43, + "y": 26 + }, + { + "x": 45, + "y": 26 + }, + { + "x": 49, + "y": 26 + }, + { + "x": 51, + "y": 26 + }, + { + "x": 53, + "y": 26 + }, + { + "x": 57, + "y": 26 + }, + { + "x": 59, + "y": 26 + }, + { + "x": 61, + "y": 26 + }, + { + "x": 65, + "y": 26 + }, + { + "x": 67, + "y": 26 + }, + { + "x": 69, + "y": 26 + }, + { + "x": 73, + "y": 26 + }, + { + "x": 75, + "y": 26 + }, + { + "x": 1, + "y": 27 + }, + { + "x": 3, + "y": 27 + }, + { + "x": 5, + "y": 27 + }, + { + "x": 9, + "y": 27 + }, + { + "x": 11, + "y": 27 + }, + { + "x": 13, + "y": 27 + }, + { + "x": 17, + "y": 27 + }, + { + "x": 19, + "y": 27 + }, + { + "x": 21, + "y": 27 + }, + { + "x": 25, + "y": 27 + }, + { + "x": 27, + "y": 27 + }, + { + "x": 29, + "y": 27 + }, + { + "x": 33, + "y": 27 + }, + { + "x": 35, + "y": 27 + }, + { + "x": 37, + "y": 27 + }, + { + "x": 41, + "y": 27 + }, + { + "x": 43, + "y": 27 + }, + { + "x": 45, + "y": 27 + }, + { + "x": 49, + "y": 27 + }, + { + "x": 51, + "y": 27 + }, + { + "x": 53, + "y": 27 + }, + { + "x": 57, + "y": 27 + }, + { + "x": 59, + "y": 27 + }, + { + "x": 61, + "y": 27 + }, + { + "x": 65, + "y": 27 + }, + { + "x": 67, + "y": 27 + }, + { + "x": 69, + "y": 27 + }, + { + "x": 73, + "y": 27 + }, + { + "x": 75, + "y": 27 + }, + { + "x": 1, + "y": 28 + }, + { + "x": 3, + "y": 28 + }, + { + "x": 5, + "y": 28 + }, + { + "x": 7, + "y": 28 + }, + { + "x": 9, + "y": 28 + }, + { + "x": 11, + "y": 28 + }, + { + "x": 13, + "y": 28 + }, + { + "x": 15, + "y": 28 + }, + { + "x": 17, + "y": 28 + }, + { + "x": 19, + "y": 28 + }, + { + "x": 21, + "y": 28 + }, + { + "x": 23, + "y": 28 + }, + { + "x": 25, + "y": 28 + }, + { + "x": 27, + "y": 28 + }, + { + "x": 29, + "y": 28 + }, + { + "x": 31, + "y": 28 + }, + { + "x": 33, + "y": 28 + }, + { + "x": 35, + "y": 28 + }, + { + "x": 37, + "y": 28 + }, + { + "x": 39, + "y": 28 + }, + { + "x": 41, + "y": 28 + }, + { + "x": 43, + "y": 28 + }, + { + "x": 45, + "y": 28 + }, + { + "x": 47, + "y": 28 + }, + { + "x": 49, + "y": 28 + }, + { + "x": 51, + "y": 28 + }, + { + "x": 53, + "y": 28 + }, + { + "x": 55, + "y": 28 + }, + { + "x": 57, + "y": 28 + }, + { + "x": 59, + "y": 28 + }, + { + "x": 61, + "y": 28 + }, + { + "x": 63, + "y": 28 + }, + { + "x": 65, + "y": 28 + }, + { + "x": 67, + "y": 28 + }, + { + "x": 69, + "y": 28 + }, + { + "x": 71, + "y": 28 + }, + { + "x": 73, + "y": 28 + }, + { + "x": 75, + "y": 28 + }, + { + "x": 1, + "y": 29 + }, + { + "x": 3, + "y": 29 + }, + { + "x": 5, + "y": 29 + }, + { + "x": 7, + "y": 29 + }, + { + "x": 9, + "y": 29 + }, + { + "x": 11, + "y": 29 + }, + { + "x": 13, + "y": 29 + }, + { + "x": 15, + "y": 29 + }, + { + "x": 17, + "y": 29 + }, + { + "x": 19, + "y": 29 + }, + { + "x": 21, + "y": 29 + }, + { + "x": 23, + "y": 29 + }, + { + "x": 25, + "y": 29 + }, + { + "x": 27, + "y": 29 + }, + { + "x": 29, + "y": 29 + }, + { + "x": 31, + "y": 29 + }, + { + "x": 33, + "y": 29 + }, + { + "x": 35, + "y": 29 + }, + { + "x": 37, + "y": 29 + }, + { + "x": 39, + "y": 29 + }, + { + "x": 41, + "y": 29 + }, + { + "x": 43, + "y": 29 + }, + { + "x": 45, + "y": 29 + }, + { + "x": 47, + "y": 29 + }, + { + "x": 49, + "y": 29 + }, + { + "x": 51, + "y": 29 + }, + { + "x": 53, + "y": 29 + }, + { + "x": 55, + "y": 29 + }, + { + "x": 57, + "y": 29 + }, + { + "x": 59, + "y": 29 + }, + { + "x": 61, + "y": 29 + }, + { + "x": 63, + "y": 29 + }, + { + "x": 65, + "y": 29 + }, + { + "x": 67, + "y": 29 + }, + { + "x": 69, + "y": 29 + }, + { + "x": 71, + "y": 29 + }, + { + "x": 73, + "y": 29 + }, + { + "x": 75, + "y": 29 + }, + { + "x": 1, + "y": 30 + }, + { + "x": 3, + "y": 30 + }, + { + "x": 5, + "y": 30 + }, + { + "x": 7, + "y": 30 + }, + { + "x": 9, + "y": 30 + }, + { + "x": 11, + "y": 30 + }, + { + "x": 13, + "y": 30 + }, + { + "x": 15, + "y": 30 + }, + { + "x": 17, + "y": 30 + }, + { + "x": 19, + "y": 30 + }, + { + "x": 21, + "y": 30 + }, + { + "x": 23, + "y": 30 + }, + { + "x": 25, + "y": 30 + }, + { + "x": 27, + "y": 30 + }, + { + "x": 29, + "y": 30 + }, + { + "x": 31, + "y": 30 + }, + { + "x": 33, + "y": 30 + }, + { + "x": 35, + "y": 30 + }, + { + "x": 37, + "y": 30 + }, + { + "x": 39, + "y": 30 + }, + { + "x": 41, + "y": 30 + }, + { + "x": 43, + "y": 30 + }, + { + "x": 45, + "y": 30 + }, + { + "x": 47, + "y": 30 + }, + { + "x": 49, + "y": 30 + }, + { + "x": 51, + "y": 30 + }, + { + "x": 53, + "y": 30 + }, + { + "x": 55, + "y": 30 + }, + { + "x": 57, + "y": 30 + }, + { + "x": 59, + "y": 30 + }, + { + "x": 61, + "y": 30 + }, + { + "x": 63, + "y": 30 + }, + { + "x": 65, + "y": 30 + }, + { + "x": 67, + "y": 30 + }, + { + "x": 69, + "y": 30 + }, + { + "x": 71, + "y": 30 + }, + { + "x": 73, + "y": 30 + }, + { + "x": 75, + "y": 30 + }, + { + "x": 1, + "y": 31 + }, + { + "x": 3, + "y": 31 + }, + { + "x": 5, + "y": 31 + }, + { + "x": 7, + "y": 31 + }, + { + "x": 9, + "y": 31 + }, + { + "x": 11, + "y": 31 + }, + { + "x": 13, + "y": 31 + }, + { + "x": 15, + "y": 31 + }, + { + "x": 17, + "y": 31 + }, + { + "x": 19, + "y": 31 + }, + { + "x": 21, + "y": 31 + }, + { + "x": 23, + "y": 31 + }, + { + "x": 25, + "y": 31 + }, + { + "x": 27, + "y": 31 + }, + { + "x": 29, + "y": 31 + }, + { + "x": 31, + "y": 31 + }, + { + "x": 33, + "y": 31 + }, + { + "x": 35, + "y": 31 + }, + { + "x": 37, + "y": 31 + }, + { + "x": 39, + "y": 31 + }, + { + "x": 41, + "y": 31 + }, + { + "x": 43, + "y": 31 + }, + { + "x": 45, + "y": 31 + }, + { + "x": 47, + "y": 31 + }, + { + "x": 49, + "y": 31 + }, + { + "x": 51, + "y": 31 + }, + { + "x": 53, + "y": 31 + }, + { + "x": 55, + "y": 31 + }, + { + "x": 57, + "y": 31 + }, + { + "x": 59, + "y": 31 + }, + { + "x": 61, + "y": 31 + }, + { + "x": 63, + "y": 31 + }, + { + "x": 65, + "y": 31 + }, + { + "x": 67, + "y": 31 + }, + { + "x": 69, + "y": 31 + }, + { + "x": 71, + "y": 31 + }, + { + "x": 73, + "y": 31 + }, + { + "x": 75, + "y": 31 + }, + { + "x": 1, + "y": 32 + }, + { + "x": 2, + "y": 32 + }, + { + "x": 3, + "y": 32 + }, + { + "x": 4, + "y": 32 + }, + { + "x": 5, + "y": 32 + }, + { + "x": 6, + "y": 32 + }, + { + "x": 7, + "y": 32 + }, + { + "x": 8, + "y": 32 + }, + { + "x": 9, + "y": 32 + }, + { + "x": 10, + "y": 32 + }, + { + "x": 11, + "y": 32 + }, + { + "x": 12, + "y": 32 + }, + { + "x": 13, + "y": 32 + }, + { + "x": 14, + "y": 32 + }, + { + "x": 15, + "y": 32 + }, + { + "x": 16, + "y": 32 + }, + { + "x": 17, + "y": 32 + }, + { + "x": 18, + "y": 32 + }, + { + "x": 19, + "y": 32 + }, + { + "x": 20, + "y": 32 + }, + { + "x": 21, + "y": 32 + }, + { + "x": 22, + "y": 32 + }, + { + "x": 23, + "y": 32 + }, + { + "x": 24, + "y": 32 + }, + { + "x": 25, + "y": 32 + }, + { + "x": 26, + "y": 32 + }, + { + "x": 27, + "y": 32 + }, + { + "x": 28, + "y": 32 + }, + { + "x": 29, + "y": 32 + }, + { + "x": 30, + "y": 32 + }, + { + "x": 31, + "y": 32 + }, + { + "x": 32, + "y": 32 + }, + { + "x": 33, + "y": 32 + }, + { + "x": 34, + "y": 32 + }, + { + "x": 35, + "y": 32 + }, + { + "x": 36, + "y": 32 + }, + { + "x": 37, + "y": 32 + }, + { + "x": 38, + "y": 32 + }, + { + "x": 39, + "y": 32 + }, + { + "x": 40, + "y": 32 + }, + { + "x": 41, + "y": 32 + }, + { + "x": 42, + "y": 32 + }, + { + "x": 43, + "y": 32 + }, + { + "x": 44, + "y": 32 + }, + { + "x": 45, + "y": 32 + }, + { + "x": 46, + "y": 32 + }, + { + "x": 47, + "y": 32 + }, + { + "x": 48, + "y": 32 + }, + { + "x": 49, + "y": 32 + }, + { + "x": 50, + "y": 32 + }, + { + "x": 51, + "y": 32 + }, + { + "x": 52, + "y": 32 + }, + { + "x": 53, + "y": 32 + }, + { + "x": 54, + "y": 32 + }, + { + "x": 55, + "y": 32 + }, + { + "x": 56, + "y": 32 + }, + { + "x": 57, + "y": 32 + }, + { + "x": 58, + "y": 32 + }, + { + "x": 59, + "y": 32 + }, + { + "x": 60, + "y": 32 + }, + { + "x": 61, + "y": 32 + }, + { + "x": 62, + "y": 32 + }, + { + "x": 63, + "y": 32 + }, + { + "x": 64, + "y": 32 + }, + { + "x": 65, + "y": 32 + }, + { + "x": 66, + "y": 32 + }, + { + "x": 67, + "y": 32 + }, + { + "x": 68, + "y": 32 + }, + { + "x": 69, + "y": 32 + }, + { + "x": 70, + "y": 32 + }, + { + "x": 71, + "y": 32 + }, + { + "x": 72, + "y": 32 + }, + { + "x": 73, + "y": 32 + }, + { + "x": 74, + "y": 32 + }, + { + "x": 75, + "y": 32 + }, + { + "x": 76, + "y": 32 + }, + { + "x": 77, + "y": 32 + }, + { + "x": 1, + "y": 33 + }, + { + "x": 3, + "y": 33 + }, + { + "x": 5, + "y": 33 + }, + { + "x": 7, + "y": 33 + }, + { + "x": 9, + "y": 33 + }, + { + "x": 11, + "y": 33 + }, + { + "x": 13, + "y": 33 + }, + { + "x": 15, + "y": 33 + }, + { + "x": 17, + "y": 33 + }, + { + "x": 19, + "y": 33 + }, + { + "x": 21, + "y": 33 + }, + { + "x": 23, + "y": 33 + }, + { + "x": 25, + "y": 33 + }, + { + "x": 27, + "y": 33 + }, + { + "x": 29, + "y": 33 + }, + { + "x": 31, + "y": 33 + }, + { + "x": 33, + "y": 33 + }, + { + "x": 35, + "y": 33 + }, + { + "x": 37, + "y": 33 + }, + { + "x": 39, + "y": 33 + }, + { + "x": 41, + "y": 33 + }, + { + "x": 43, + "y": 33 + }, + { + "x": 45, + "y": 33 + }, + { + "x": 47, + "y": 33 + }, + { + "x": 49, + "y": 33 + }, + { + "x": 51, + "y": 33 + }, + { + "x": 53, + "y": 33 + }, + { + "x": 55, + "y": 33 + }, + { + "x": 57, + "y": 33 + }, + { + "x": 59, + "y": 33 + }, + { + "x": 61, + "y": 33 + }, + { + "x": 63, + "y": 33 + }, + { + "x": 65, + "y": 33 + }, + { + "x": 67, + "y": 33 + }, + { + "x": 69, + "y": 33 + }, + { + "x": 71, + "y": 33 + }, + { + "x": 73, + "y": 33 + }, + { + "x": 75, + "y": 33 + }, + { + "x": 77, + "y": 33 + }, + { + "x": 1, + "y": 34 + }, + { + "x": 3, + "y": 34 + }, + { + "x": 5, + "y": 34 + }, + { + "x": 7, + "y": 34 + }, + { + "x": 9, + "y": 34 + }, + { + "x": 11, + "y": 34 + }, + { + "x": 13, + "y": 34 + }, + { + "x": 15, + "y": 34 + }, + { + "x": 17, + "y": 34 + }, + { + "x": 19, + "y": 34 + }, + { + "x": 21, + "y": 34 + }, + { + "x": 23, + "y": 34 + }, + { + "x": 25, + "y": 34 + }, + { + "x": 27, + "y": 34 + }, + { + "x": 29, + "y": 34 + }, + { + "x": 31, + "y": 34 + }, + { + "x": 33, + "y": 34 + }, + { + "x": 35, + "y": 34 + }, + { + "x": 37, + "y": 34 + }, + { + "x": 39, + "y": 34 + }, + { + "x": 41, + "y": 34 + }, + { + "x": 43, + "y": 34 + }, + { + "x": 45, + "y": 34 + }, + { + "x": 47, + "y": 34 + }, + { + "x": 49, + "y": 34 + }, + { + "x": 51, + "y": 34 + }, + { + "x": 53, + "y": 34 + }, + { + "x": 55, + "y": 34 + }, + { + "x": 57, + "y": 34 + }, + { + "x": 59, + "y": 34 + }, + { + "x": 61, + "y": 34 + }, + { + "x": 63, + "y": 34 + }, + { + "x": 65, + "y": 34 + }, + { + "x": 67, + "y": 34 + }, + { + "x": 69, + "y": 34 + }, + { + "x": 71, + "y": 34 + }, + { + "x": 73, + "y": 34 + }, + { + "x": 75, + "y": 34 + }, + { + "x": 77, + "y": 34 + }, + { + "x": 1, + "y": 35 + }, + { + "x": 3, + "y": 35 + }, + { + "x": 5, + "y": 35 + }, + { + "x": 7, + "y": 35 + }, + { + "x": 9, + "y": 35 + }, + { + "x": 11, + "y": 35 + }, + { + "x": 13, + "y": 35 + }, + { + "x": 15, + "y": 35 + }, + { + "x": 17, + "y": 35 + }, + { + "x": 19, + "y": 35 + }, + { + "x": 21, + "y": 35 + }, + { + "x": 23, + "y": 35 + }, + { + "x": 25, + "y": 35 + }, + { + "x": 27, + "y": 35 + }, + { + "x": 29, + "y": 35 + }, + { + "x": 31, + "y": 35 + }, + { + "x": 33, + "y": 35 + }, + { + "x": 35, + "y": 35 + }, + { + "x": 37, + "y": 35 + }, + { + "x": 39, + "y": 35 + }, + { + "x": 41, + "y": 35 + }, + { + "x": 43, + "y": 35 + }, + { + "x": 45, + "y": 35 + }, + { + "x": 47, + "y": 35 + }, + { + "x": 49, + "y": 35 + }, + { + "x": 51, + "y": 35 + }, + { + "x": 53, + "y": 35 + }, + { + "x": 55, + "y": 35 + }, + { + "x": 57, + "y": 35 + }, + { + "x": 59, + "y": 35 + }, + { + "x": 61, + "y": 35 + }, + { + "x": 63, + "y": 35 + }, + { + "x": 65, + "y": 35 + }, + { + "x": 67, + "y": 35 + }, + { + "x": 69, + "y": 35 + }, + { + "x": 71, + "y": 35 + }, + { + "x": 73, + "y": 35 + }, + { + "x": 75, + "y": 35 + }, + { + "x": 77, + "y": 35 + }, + { + "x": 1, + "y": 36 + }, + { + "x": 3, + "y": 36 + }, + { + "x": 5, + "y": 36 + }, + { + "x": 7, + "y": 36 + }, + { + "x": 9, + "y": 36 + }, + { + "x": 11, + "y": 36 + }, + { + "x": 13, + "y": 36 + }, + { + "x": 15, + "y": 36 + }, + { + "x": 17, + "y": 36 + }, + { + "x": 19, + "y": 36 + }, + { + "x": 21, + "y": 36 + }, + { + "x": 23, + "y": 36 + }, + { + "x": 25, + "y": 36 + }, + { + "x": 27, + "y": 36 + }, + { + "x": 29, + "y": 36 + }, + { + "x": 31, + "y": 36 + }, + { + "x": 33, + "y": 36 + }, + { + "x": 35, + "y": 36 + }, + { + "x": 37, + "y": 36 + }, + { + "x": 39, + "y": 36 + }, + { + "x": 41, + "y": 36 + }, + { + "x": 43, + "y": 36 + }, + { + "x": 45, + "y": 36 + }, + { + "x": 47, + "y": 36 + }, + { + "x": 49, + "y": 36 + }, + { + "x": 51, + "y": 36 + }, + { + "x": 53, + "y": 36 + }, + { + "x": 55, + "y": 36 + }, + { + "x": 57, + "y": 36 + }, + { + "x": 59, + "y": 36 + }, + { + "x": 61, + "y": 36 + }, + { + "x": 63, + "y": 36 + }, + { + "x": 65, + "y": 36 + }, + { + "x": 67, + "y": 36 + }, + { + "x": 69, + "y": 36 + }, + { + "x": 71, + "y": 36 + }, + { + "x": 73, + "y": 36 + }, + { + "x": 75, + "y": 36 + }, + { + "x": 77, + "y": 36 + }, + { + "x": 1, + "y": 37 + }, + { + "x": 3, + "y": 37 + }, + { + "x": 5, + "y": 37 + }, + { + "x": 7, + "y": 37 + }, + { + "x": 9, + "y": 37 + }, + { + "x": 11, + "y": 37 + }, + { + "x": 13, + "y": 37 + }, + { + "x": 15, + "y": 37 + }, + { + "x": 17, + "y": 37 + }, + { + "x": 19, + "y": 37 + }, + { + "x": 21, + "y": 37 + }, + { + "x": 23, + "y": 37 + }, + { + "x": 25, + "y": 37 + }, + { + "x": 27, + "y": 37 + }, + { + "x": 29, + "y": 37 + }, + { + "x": 31, + "y": 37 + }, + { + "x": 33, + "y": 37 + }, + { + "x": 35, + "y": 37 + }, + { + "x": 37, + "y": 37 + }, + { + "x": 39, + "y": 37 + }, + { + "x": 41, + "y": 37 + }, + { + "x": 43, + "y": 37 + }, + { + "x": 45, + "y": 37 + }, + { + "x": 47, + "y": 37 + }, + { + "x": 49, + "y": 37 + }, + { + "x": 51, + "y": 37 + }, + { + "x": 53, + "y": 37 + }, + { + "x": 55, + "y": 37 + }, + { + "x": 57, + "y": 37 + }, + { + "x": 59, + "y": 37 + }, + { + "x": 61, + "y": 37 + }, + { + "x": 63, + "y": 37 + }, + { + "x": 65, + "y": 37 + }, + { + "x": 67, + "y": 37 + }, + { + "x": 69, + "y": 37 + }, + { + "x": 71, + "y": 37 + }, + { + "x": 73, + "y": 37 + }, + { + "x": 75, + "y": 37 + }, + { + "x": 77, + "y": 37 + }, + { + "x": 1, + "y": 38 + }, + { + "x": 3, + "y": 38 + }, + { + "x": 5, + "y": 38 + }, + { + "x": 7, + "y": 38 + }, + { + "x": 9, + "y": 38 + }, + { + "x": 11, + "y": 38 + }, + { + "x": 13, + "y": 38 + }, + { + "x": 15, + "y": 38 + }, + { + "x": 17, + "y": 38 + }, + { + "x": 19, + "y": 38 + }, + { + "x": 21, + "y": 38 + }, + { + "x": 23, + "y": 38 + }, + { + "x": 25, + "y": 38 + }, + { + "x": 27, + "y": 38 + }, + { + "x": 29, + "y": 38 + }, + { + "x": 31, + "y": 38 + }, + { + "x": 33, + "y": 38 + }, + { + "x": 35, + "y": 38 + }, + { + "x": 37, + "y": 38 + }, + { + "x": 39, + "y": 38 + }, + { + "x": 41, + "y": 38 + }, + { + "x": 43, + "y": 38 + }, + { + "x": 45, + "y": 38 + }, + { + "x": 47, + "y": 38 + }, + { + "x": 49, + "y": 38 + }, + { + "x": 51, + "y": 38 + }, + { + "x": 53, + "y": 38 + }, + { + "x": 55, + "y": 38 + }, + { + "x": 57, + "y": 38 + }, + { + "x": 59, + "y": 38 + }, + { + "x": 61, + "y": 38 + }, + { + "x": 63, + "y": 38 + }, + { + "x": 65, + "y": 38 + }, + { + "x": 67, + "y": 38 + }, + { + "x": 69, + "y": 38 + }, + { + "x": 71, + "y": 38 + }, + { + "x": 73, + "y": 38 + }, + { + "x": 75, + "y": 38 + }, + { + "x": 77, + "y": 38 + }, + { + "x": 1, + "y": 39 + }, + { + "x": 3, + "y": 39 + }, + { + "x": 5, + "y": 39 + }, + { + "x": 7, + "y": 39 + }, + { + "x": 9, + "y": 39 + }, + { + "x": 11, + "y": 39 + }, + { + "x": 13, + "y": 39 + }, + { + "x": 15, + "y": 39 + }, + { + "x": 17, + "y": 39 + }, + { + "x": 19, + "y": 39 + }, + { + "x": 21, + "y": 39 + }, + { + "x": 23, + "y": 39 + }, + { + "x": 25, + "y": 39 + }, + { + "x": 27, + "y": 39 + }, + { + "x": 29, + "y": 39 + }, + { + "x": 31, + "y": 39 + }, + { + "x": 33, + "y": 39 + }, + { + "x": 35, + "y": 39 + }, + { + "x": 37, + "y": 39 + }, + { + "x": 39, + "y": 39 + }, + { + "x": 41, + "y": 39 + }, + { + "x": 43, + "y": 39 + }, + { + "x": 45, + "y": 39 + }, + { + "x": 47, + "y": 39 + }, + { + "x": 49, + "y": 39 + }, + { + "x": 51, + "y": 39 + }, + { + "x": 53, + "y": 39 + }, + { + "x": 55, + "y": 39 + }, + { + "x": 57, + "y": 39 + }, + { + "x": 59, + "y": 39 + }, + { + "x": 61, + "y": 39 + }, + { + "x": 63, + "y": 39 + }, + { + "x": 65, + "y": 39 + }, + { + "x": 67, + "y": 39 + }, + { + "x": 69, + "y": 39 + }, + { + "x": 71, + "y": 39 + }, + { + "x": 73, + "y": 39 + }, + { + "x": 75, + "y": 39 + }, + { + "x": 77, + "y": 39 + }, + { + "x": 1, + "y": 40 + }, + { + "x": 3, + "y": 40 + }, + { + "x": 5, + "y": 40 + }, + { + "x": 7, + "y": 40 + }, + { + "x": 9, + "y": 40 + }, + { + "x": 11, + "y": 40 + }, + { + "x": 13, + "y": 40 + }, + { + "x": 15, + "y": 40 + }, + { + "x": 17, + "y": 40 + }, + { + "x": 19, + "y": 40 + }, + { + "x": 21, + "y": 40 + }, + { + "x": 23, + "y": 40 + }, + { + "x": 25, + "y": 40 + }, + { + "x": 27, + "y": 40 + }, + { + "x": 29, + "y": 40 + }, + { + "x": 31, + "y": 40 + }, + { + "x": 33, + "y": 40 + }, + { + "x": 35, + "y": 40 + }, + { + "x": 37, + "y": 40 + }, + { + "x": 39, + "y": 40 + }, + { + "x": 41, + "y": 40 + }, + { + "x": 43, + "y": 40 + }, + { + "x": 45, + "y": 40 + }, + { + "x": 47, + "y": 40 + }, + { + "x": 49, + "y": 40 + }, + { + "x": 51, + "y": 40 + }, + { + "x": 53, + "y": 40 + }, + { + "x": 55, + "y": 40 + }, + { + "x": 57, + "y": 40 + }, + { + "x": 59, + "y": 40 + }, + { + "x": 61, + "y": 40 + }, + { + "x": 63, + "y": 40 + }, + { + "x": 65, + "y": 40 + }, + { + "x": 67, + "y": 40 + }, + { + "x": 69, + "y": 40 + }, + { + "x": 71, + "y": 40 + }, + { + "x": 73, + "y": 40 + }, + { + "x": 75, + "y": 40 + }, + { + "x": 77, + "y": 40 + }, + { + "x": 1, + "y": 41 + }, + { + "x": 3, + "y": 41 + }, + { + "x": 5, + "y": 41 + }, + { + "x": 7, + "y": 41 + }, + { + "x": 9, + "y": 41 + }, + { + "x": 11, + "y": 41 + }, + { + "x": 13, + "y": 41 + }, + { + "x": 15, + "y": 41 + }, + { + "x": 17, + "y": 41 + }, + { + "x": 19, + "y": 41 + }, + { + "x": 21, + "y": 41 + }, + { + "x": 23, + "y": 41 + }, + { + "x": 25, + "y": 41 + }, + { + "x": 27, + "y": 41 + }, + { + "x": 29, + "y": 41 + }, + { + "x": 31, + "y": 41 + }, + { + "x": 33, + "y": 41 + }, + { + "x": 35, + "y": 41 + }, + { + "x": 37, + "y": 41 + }, + { + "x": 39, + "y": 41 + }, + { + "x": 41, + "y": 41 + }, + { + "x": 43, + "y": 41 + }, + { + "x": 45, + "y": 41 + }, + { + "x": 47, + "y": 41 + }, + { + "x": 49, + "y": 41 + }, + { + "x": 51, + "y": 41 + }, + { + "x": 53, + "y": 41 + }, + { + "x": 55, + "y": 41 + }, + { + "x": 57, + "y": 41 + }, + { + "x": 59, + "y": 41 + }, + { + "x": 61, + "y": 41 + }, + { + "x": 63, + "y": 41 + }, + { + "x": 65, + "y": 41 + }, + { + "x": 67, + "y": 41 + }, + { + "x": 69, + "y": 41 + }, + { + "x": 71, + "y": 41 + }, + { + "x": 73, + "y": 41 + }, + { + "x": 75, + "y": 41 + }, + { + "x": 77, + "y": 41 + }, + { + "x": 1, + "y": 42 + }, + { + "x": 2, + "y": 42 + }, + { + "x": 3, + "y": 42 + }, + { + "x": 4, + "y": 42 + }, + { + "x": 5, + "y": 42 + }, + { + "x": 6, + "y": 42 + }, + { + "x": 7, + "y": 42 + }, + { + "x": 8, + "y": 42 + }, + { + "x": 9, + "y": 42 + }, + { + "x": 10, + "y": 42 + }, + { + "x": 11, + "y": 42 + }, + { + "x": 12, + "y": 42 + }, + { + "x": 13, + "y": 42 + }, + { + "x": 14, + "y": 42 + }, + { + "x": 15, + "y": 42 + }, + { + "x": 16, + "y": 42 + }, + { + "x": 17, + "y": 42 + }, + { + "x": 18, + "y": 42 + }, + { + "x": 19, + "y": 42 + }, + { + "x": 20, + "y": 42 + }, + { + "x": 21, + "y": 42 + }, + { + "x": 22, + "y": 42 + }, + { + "x": 23, + "y": 42 + }, + { + "x": 24, + "y": 42 + }, + { + "x": 25, + "y": 42 + }, + { + "x": 26, + "y": 42 + }, + { + "x": 27, + "y": 42 + }, + { + "x": 28, + "y": 42 + }, + { + "x": 29, + "y": 42 + }, + { + "x": 30, + "y": 42 + }, + { + "x": 31, + "y": 42 + }, + { + "x": 32, + "y": 42 + }, + { + "x": 33, + "y": 42 + }, + { + "x": 34, + "y": 42 + }, + { + "x": 35, + "y": 42 + }, + { + "x": 36, + "y": 42 + }, + { + "x": 37, + "y": 42 + }, + { + "x": 38, + "y": 42 + }, + { + "x": 39, + "y": 42 + }, + { + "x": 40, + "y": 42 + }, + { + "x": 41, + "y": 42 + }, + { + "x": 42, + "y": 42 + }, + { + "x": 43, + "y": 42 + }, + { + "x": 44, + "y": 42 + }, + { + "x": 45, + "y": 42 + }, + { + "x": 46, + "y": 42 + }, + { + "x": 47, + "y": 42 + }, + { + "x": 48, + "y": 42 + }, + { + "x": 49, + "y": 42 + }, + { + "x": 50, + "y": 42 + }, + { + "x": 51, + "y": 42 + }, + { + "x": 52, + "y": 42 + }, + { + "x": 53, + "y": 42 + }, + { + "x": 54, + "y": 42 + }, + { + "x": 55, + "y": 42 + }, + { + "x": 56, + "y": 42 + }, + { + "x": 57, + "y": 42 + }, + { + "x": 58, + "y": 42 + }, + { + "x": 59, + "y": 42 + }, + { + "x": 60, + "y": 42 + }, + { + "x": 61, + "y": 42 + }, + { + "x": 62, + "y": 42 + }, + { + "x": 63, + "y": 42 + }, + { + "x": 64, + "y": 42 + }, + { + "x": 65, + "y": 42 + }, + { + "x": 66, + "y": 42 + }, + { + "x": 67, + "y": 42 + }, + { + "x": 68, + "y": 42 + }, + { + "x": 69, + "y": 42 + }, + { + "x": 70, + "y": 42 + }, + { + "x": 71, + "y": 42 + }, + { + "x": 72, + "y": 42 + }, + { + "x": 73, + "y": 42 + }, + { + "x": 74, + "y": 42 + }, + { + "x": 75, + "y": 42 + }, + { + "x": 76, + "y": 42 + }, + { + "x": 77, + "y": 42 + }, + { + "x": 1, + "y": 43 + }, + { + "x": 3, + "y": 43 + }, + { + "x": 5, + "y": 43 + }, + { + "x": 9, + "y": 43 + }, + { + "x": 11, + "y": 43 + }, + { + "x": 13, + "y": 43 + }, + { + "x": 17, + "y": 43 + }, + { + "x": 19, + "y": 43 + }, + { + "x": 21, + "y": 43 + }, + { + "x": 25, + "y": 43 + }, + { + "x": 27, + "y": 43 + }, + { + "x": 29, + "y": 43 + }, + { + "x": 33, + "y": 43 + }, + { + "x": 35, + "y": 43 + }, + { + "x": 37, + "y": 43 + }, + { + "x": 41, + "y": 43 + }, + { + "x": 43, + "y": 43 + }, + { + "x": 45, + "y": 43 + }, + { + "x": 49, + "y": 43 + }, + { + "x": 51, + "y": 43 + }, + { + "x": 53, + "y": 43 + }, + { + "x": 57, + "y": 43 + }, + { + "x": 59, + "y": 43 + }, + { + "x": 61, + "y": 43 + }, + { + "x": 65, + "y": 43 + }, + { + "x": 67, + "y": 43 + }, + { + "x": 69, + "y": 43 + }, + { + "x": 73, + "y": 43 + }, + { + "x": 75, + "y": 43 + }, + { + "x": 77, + "y": 43 + }, + { + "x": 1, + "y": 44 + }, + { + "x": 2, + "y": 44 + }, + { + "x": 3, + "y": 44 + }, + { + "x": 4, + "y": 44 + }, + { + "x": 5, + "y": 44 + }, + { + "x": 6, + "y": 44 + }, + { + "x": 7, + "y": 44 + }, + { + "x": 8, + "y": 44 + }, + { + "x": 9, + "y": 44 + }, + { + "x": 10, + "y": 44 + }, + { + "x": 11, + "y": 44 + }, + { + "x": 12, + "y": 44 + }, + { + "x": 13, + "y": 44 + }, + { + "x": 14, + "y": 44 + }, + { + "x": 15, + "y": 44 + }, + { + "x": 16, + "y": 44 + }, + { + "x": 17, + "y": 44 + }, + { + "x": 18, + "y": 44 + }, + { + "x": 19, + "y": 44 + }, + { + "x": 20, + "y": 44 + }, + { + "x": 21, + "y": 44 + }, + { + "x": 22, + "y": 44 + }, + { + "x": 23, + "y": 44 + }, + { + "x": 24, + "y": 44 + }, + { + "x": 25, + "y": 44 + }, + { + "x": 26, + "y": 44 + }, + { + "x": 27, + "y": 44 + }, + { + "x": 28, + "y": 44 + }, + { + "x": 29, + "y": 44 + }, + { + "x": 30, + "y": 44 + }, + { + "x": 31, + "y": 44 + }, + { + "x": 32, + "y": 44 + }, + { + "x": 33, + "y": 44 + }, + { + "x": 34, + "y": 44 + }, + { + "x": 35, + "y": 44 + }, + { + "x": 36, + "y": 44 + }, + { + "x": 37, + "y": 44 + }, + { + "x": 38, + "y": 44 + }, + { + "x": 39, + "y": 44 + }, + { + "x": 40, + "y": 44 + }, + { + "x": 41, + "y": 44 + }, + { + "x": 42, + "y": 44 + }, + { + "x": 43, + "y": 44 + }, + { + "x": 44, + "y": 44 + }, + { + "x": 45, + "y": 44 + }, + { + "x": 46, + "y": 44 + }, + { + "x": 47, + "y": 44 + }, + { + "x": 48, + "y": 44 + }, + { + "x": 49, + "y": 44 + }, + { + "x": 50, + "y": 44 + }, + { + "x": 51, + "y": 44 + }, + { + "x": 52, + "y": 44 + }, + { + "x": 53, + "y": 44 + }, + { + "x": 54, + "y": 44 + }, + { + "x": 55, + "y": 44 + }, + { + "x": 56, + "y": 44 + }, + { + "x": 57, + "y": 44 + }, + { + "x": 58, + "y": 44 + }, + { + "x": 59, + "y": 44 + }, + { + "x": 60, + "y": 44 + }, + { + "x": 61, + "y": 44 + }, + { + "x": 62, + "y": 44 + }, + { + "x": 63, + "y": 44 + }, + { + "x": 64, + "y": 44 + }, + { + "x": 65, + "y": 44 + }, + { + "x": 66, + "y": 44 + }, + { + "x": 67, + "y": 44 + }, + { + "x": 68, + "y": 44 + }, + { + "x": 69, + "y": 44 + }, + { + "x": 70, + "y": 44 + }, + { + "x": 71, + "y": 44 + }, + { + "x": 72, + "y": 44 + }, + { + "x": 73, + "y": 44 + }, + { + "x": 74, + "y": 44 + }, + { + "x": 75, + "y": 44 + }, + { + "x": 76, + "y": 44 + }, + { + "x": 77, + "y": 44 + }, + { + "x": 1, + "y": 45 + }, + { + "x": 3, + "y": 45 + }, + { + "x": 5, + "y": 45 + }, + { + "x": 9, + "y": 45 + }, + { + "x": 11, + "y": 45 + }, + { + "x": 13, + "y": 45 + }, + { + "x": 17, + "y": 45 + }, + { + "x": 19, + "y": 45 + }, + { + "x": 21, + "y": 45 + }, + { + "x": 25, + "y": 45 + }, + { + "x": 27, + "y": 45 + }, + { + "x": 29, + "y": 45 + }, + { + "x": 33, + "y": 45 + }, + { + "x": 35, + "y": 45 + }, + { + "x": 37, + "y": 45 + }, + { + "x": 41, + "y": 45 + }, + { + "x": 43, + "y": 45 + }, + { + "x": 45, + "y": 45 + }, + { + "x": 49, + "y": 45 + }, + { + "x": 51, + "y": 45 + }, + { + "x": 53, + "y": 45 + }, + { + "x": 57, + "y": 45 + }, + { + "x": 59, + "y": 45 + }, + { + "x": 61, + "y": 45 + }, + { + "x": 65, + "y": 45 + }, + { + "x": 67, + "y": 45 + }, + { + "x": 69, + "y": 45 + }, + { + "x": 73, + "y": 45 + }, + { + "x": 75, + "y": 45 + }, + { + "x": 77, + "y": 45 + }, + { + "x": 1, + "y": 46 + }, + { + "x": 3, + "y": 46 + }, + { + "x": 5, + "y": 46 + }, + { + "x": 9, + "y": 46 + }, + { + "x": 11, + "y": 46 + }, + { + "x": 13, + "y": 46 + }, + { + "x": 17, + "y": 46 + }, + { + "x": 19, + "y": 46 + }, + { + "x": 21, + "y": 46 + }, + { + "x": 25, + "y": 46 + }, + { + "x": 27, + "y": 46 + }, + { + "x": 29, + "y": 46 + }, + { + "x": 33, + "y": 46 + }, + { + "x": 35, + "y": 46 + }, + { + "x": 37, + "y": 46 + }, + { + "x": 41, + "y": 46 + }, + { + "x": 43, + "y": 46 + }, + { + "x": 45, + "y": 46 + }, + { + "x": 49, + "y": 46 + }, + { + "x": 51, + "y": 46 + }, + { + "x": 53, + "y": 46 + }, + { + "x": 57, + "y": 46 + }, + { + "x": 59, + "y": 46 + }, + { + "x": 61, + "y": 46 + }, + { + "x": 65, + "y": 46 + }, + { + "x": 67, + "y": 46 + }, + { + "x": 69, + "y": 46 + }, + { + "x": 73, + "y": 46 + }, + { + "x": 75, + "y": 46 + }, + { + "x": 77, + "y": 46 + }, + { + "x": 1, + "y": 47 + }, + { + "x": 2, + "y": 47 + }, + { + "x": 3, + "y": 47 + }, + { + "x": 4, + "y": 47 + }, + { + "x": 5, + "y": 47 + }, + { + "x": 6, + "y": 47 + }, + { + "x": 7, + "y": 47 + }, + { + "x": 8, + "y": 47 + }, + { + "x": 9, + "y": 47 + }, + { + "x": 11, + "y": 47 + }, + { + "x": 13, + "y": 47 + }, + { + "x": 17, + "y": 47 + }, + { + "x": 18, + "y": 47 + }, + { + "x": 19, + "y": 47 + }, + { + "x": 20, + "y": 47 + }, + { + "x": 21, + "y": 47 + }, + { + "x": 22, + "y": 47 + }, + { + "x": 23, + "y": 47 + }, + { + "x": 24, + "y": 47 + }, + { + "x": 25, + "y": 47 + }, + { + "x": 27, + "y": 47 + }, + { + "x": 29, + "y": 47 + }, + { + "x": 33, + "y": 47 + }, + { + "x": 34, + "y": 47 + }, + { + "x": 35, + "y": 47 + }, + { + "x": 36, + "y": 47 + }, + { + "x": 37, + "y": 47 + }, + { + "x": 38, + "y": 47 + }, + { + "x": 39, + "y": 47 + }, + { + "x": 40, + "y": 47 + }, + { + "x": 41, + "y": 47 + }, + { + "x": 43, + "y": 47 + }, + { + "x": 45, + "y": 47 + }, + { + "x": 49, + "y": 47 + }, + { + "x": 50, + "y": 47 + }, + { + "x": 51, + "y": 47 + }, + { + "x": 52, + "y": 47 + }, + { + "x": 53, + "y": 47 + }, + { + "x": 54, + "y": 47 + }, + { + "x": 55, + "y": 47 + }, + { + "x": 56, + "y": 47 + }, + { + "x": 57, + "y": 47 + }, + { + "x": 59, + "y": 47 + }, + { + "x": 61, + "y": 47 + }, + { + "x": 65, + "y": 47 + }, + { + "x": 66, + "y": 47 + }, + { + "x": 67, + "y": 47 + }, + { + "x": 68, + "y": 47 + }, + { + "x": 69, + "y": 47 + }, + { + "x": 70, + "y": 47 + }, + { + "x": 71, + "y": 47 + }, + { + "x": 72, + "y": 47 + }, + { + "x": 73, + "y": 47 + }, + { + "x": 75, + "y": 47 + }, + { + "x": 77, + "y": 47 + } + ], + "storage_areas": [ + { + "x": 18, + "y": 1 + }, + { + "x": 20, + "y": 1 + }, + { + "x": 22, + "y": 1 + }, + { + "x": 24, + "y": 1 + }, + { + "x": 26, + "y": 1 + }, + { + "x": 28, + "y": 1 + }, + { + "x": 30, + "y": 1 + }, + { + "x": 32, + "y": 1 + }, + { + "x": 34, + "y": 1 + }, + { + "x": 36, + "y": 1 + }, + { + "x": 38, + "y": 1 + }, + { + "x": 40, + "y": 1 + }, + { + "x": 42, + "y": 1 + }, + { + "x": 44, + "y": 1 + }, + { + "x": 46, + "y": 1 + }, + { + "x": 48, + "y": 1 + }, + { + "x": 50, + "y": 1 + }, + { + "x": 52, + "y": 1 + }, + { + "x": 54, + "y": 1 + }, + { + "x": 56, + "y": 1 + }, + { + "x": 58, + "y": 1 + }, + { + "x": 60, + "y": 1 + }, + { + "x": 18, + "y": 2 + }, + { + "x": 20, + "y": 2 + }, + { + "x": 22, + "y": 2 + }, + { + "x": 24, + "y": 2 + }, + { + "x": 26, + "y": 2 + }, + { + "x": 28, + "y": 2 + }, + { + "x": 30, + "y": 2 + }, + { + "x": 32, + "y": 2 + }, + { + "x": 34, + "y": 2 + }, + { + "x": 36, + "y": 2 + }, + { + "x": 38, + "y": 2 + }, + { + "x": 40, + "y": 2 + }, + { + "x": 42, + "y": 2 + }, + { + "x": 44, + "y": 2 + }, + { + "x": 46, + "y": 2 + }, + { + "x": 48, + "y": 2 + }, + { + "x": 50, + "y": 2 + }, + { + "x": 52, + "y": 2 + }, + { + "x": 54, + "y": 2 + }, + { + "x": 56, + "y": 2 + }, + { + "x": 58, + "y": 2 + }, + { + "x": 60, + "y": 2 + }, + { + "x": 18, + "y": 3 + }, + { + "x": 20, + "y": 3 + }, + { + "x": 22, + "y": 3 + }, + { + "x": 24, + "y": 3 + }, + { + "x": 26, + "y": 3 + }, + { + "x": 28, + "y": 3 + }, + { + "x": 30, + "y": 3 + }, + { + "x": 32, + "y": 3 + }, + { + "x": 34, + "y": 3 + }, + { + "x": 36, + "y": 3 + }, + { + "x": 38, + "y": 3 + }, + { + "x": 40, + "y": 3 + }, + { + "x": 42, + "y": 3 + }, + { + "x": 44, + "y": 3 + }, + { + "x": 46, + "y": 3 + }, + { + "x": 48, + "y": 3 + }, + { + "x": 50, + "y": 3 + }, + { + "x": 52, + "y": 3 + }, + { + "x": 54, + "y": 3 + }, + { + "x": 56, + "y": 3 + }, + { + "x": 58, + "y": 3 + }, + { + "x": 60, + "y": 3 + }, + { + "x": 18, + "y": 4 + }, + { + "x": 20, + "y": 4 + }, + { + "x": 22, + "y": 4 + }, + { + "x": 24, + "y": 4 + }, + { + "x": 26, + "y": 4 + }, + { + "x": 28, + "y": 4 + }, + { + "x": 30, + "y": 4 + }, + { + "x": 32, + "y": 4 + }, + { + "x": 34, + "y": 4 + }, + { + "x": 36, + "y": 4 + }, + { + "x": 38, + "y": 4 + }, + { + "x": 40, + "y": 4 + }, + { + "x": 42, + "y": 4 + }, + { + "x": 44, + "y": 4 + }, + { + "x": 46, + "y": 4 + }, + { + "x": 48, + "y": 4 + }, + { + "x": 50, + "y": 4 + }, + { + "x": 52, + "y": 4 + }, + { + "x": 54, + "y": 4 + }, + { + "x": 56, + "y": 4 + }, + { + "x": 58, + "y": 4 + }, + { + "x": 60, + "y": 4 + }, + { + "x": 18, + "y": 5 + }, + { + "x": 20, + "y": 5 + }, + { + "x": 22, + "y": 5 + }, + { + "x": 24, + "y": 5 + }, + { + "x": 26, + "y": 5 + }, + { + "x": 28, + "y": 5 + }, + { + "x": 30, + "y": 5 + }, + { + "x": 32, + "y": 5 + }, + { + "x": 34, + "y": 5 + }, + { + "x": 36, + "y": 5 + }, + { + "x": 38, + "y": 5 + }, + { + "x": 40, + "y": 5 + }, + { + "x": 42, + "y": 5 + }, + { + "x": 44, + "y": 5 + }, + { + "x": 46, + "y": 5 + }, + { + "x": 48, + "y": 5 + }, + { + "x": 50, + "y": 5 + }, + { + "x": 52, + "y": 5 + }, + { + "x": 54, + "y": 5 + }, + { + "x": 56, + "y": 5 + }, + { + "x": 58, + "y": 5 + }, + { + "x": 60, + "y": 5 + }, + { + "x": 18, + "y": 7 + }, + { + "x": 20, + "y": 7 + }, + { + "x": 22, + "y": 7 + }, + { + "x": 24, + "y": 7 + }, + { + "x": 26, + "y": 7 + }, + { + "x": 28, + "y": 7 + }, + { + "x": 30, + "y": 7 + }, + { + "x": 32, + "y": 7 + }, + { + "x": 34, + "y": 7 + }, + { + "x": 36, + "y": 7 + }, + { + "x": 38, + "y": 7 + }, + { + "x": 40, + "y": 7 + }, + { + "x": 42, + "y": 7 + }, + { + "x": 44, + "y": 7 + }, + { + "x": 46, + "y": 7 + }, + { + "x": 48, + "y": 7 + }, + { + "x": 50, + "y": 7 + }, + { + "x": 52, + "y": 7 + }, + { + "x": 54, + "y": 7 + }, + { + "x": 56, + "y": 7 + }, + { + "x": 58, + "y": 7 + }, + { + "x": 60, + "y": 7 + }, + { + "x": 18, + "y": 8 + }, + { + "x": 20, + "y": 8 + }, + { + "x": 22, + "y": 8 + }, + { + "x": 24, + "y": 8 + }, + { + "x": 26, + "y": 8 + }, + { + "x": 28, + "y": 8 + }, + { + "x": 30, + "y": 8 + }, + { + "x": 32, + "y": 8 + }, + { + "x": 34, + "y": 8 + }, + { + "x": 36, + "y": 8 + }, + { + "x": 38, + "y": 8 + }, + { + "x": 40, + "y": 8 + }, + { + "x": 42, + "y": 8 + }, + { + "x": 44, + "y": 8 + }, + { + "x": 46, + "y": 8 + }, + { + "x": 48, + "y": 8 + }, + { + "x": 50, + "y": 8 + }, + { + "x": 52, + "y": 8 + }, + { + "x": 54, + "y": 8 + }, + { + "x": 56, + "y": 8 + }, + { + "x": 58, + "y": 8 + }, + { + "x": 60, + "y": 8 + }, + { + "x": 18, + "y": 9 + }, + { + "x": 20, + "y": 9 + }, + { + "x": 22, + "y": 9 + }, + { + "x": 24, + "y": 9 + }, + { + "x": 26, + "y": 9 + }, + { + "x": 28, + "y": 9 + }, + { + "x": 30, + "y": 9 + }, + { + "x": 32, + "y": 9 + }, + { + "x": 34, + "y": 9 + }, + { + "x": 36, + "y": 9 + }, + { + "x": 38, + "y": 9 + }, + { + "x": 40, + "y": 9 + }, + { + "x": 42, + "y": 9 + }, + { + "x": 44, + "y": 9 + }, + { + "x": 46, + "y": 9 + }, + { + "x": 48, + "y": 9 + }, + { + "x": 50, + "y": 9 + }, + { + "x": 52, + "y": 9 + }, + { + "x": 54, + "y": 9 + }, + { + "x": 56, + "y": 9 + }, + { + "x": 58, + "y": 9 + }, + { + "x": 60, + "y": 9 + }, + { + "x": 18, + "y": 10 + }, + { + "x": 20, + "y": 10 + }, + { + "x": 22, + "y": 10 + }, + { + "x": 24, + "y": 10 + }, + { + "x": 26, + "y": 10 + }, + { + "x": 28, + "y": 10 + }, + { + "x": 30, + "y": 10 + }, + { + "x": 32, + "y": 10 + }, + { + "x": 34, + "y": 10 + }, + { + "x": 36, + "y": 10 + }, + { + "x": 38, + "y": 10 + }, + { + "x": 40, + "y": 10 + }, + { + "x": 42, + "y": 10 + }, + { + "x": 44, + "y": 10 + }, + { + "x": 46, + "y": 10 + }, + { + "x": 48, + "y": 10 + }, + { + "x": 50, + "y": 10 + }, + { + "x": 52, + "y": 10 + }, + { + "x": 54, + "y": 10 + }, + { + "x": 56, + "y": 10 + }, + { + "x": 58, + "y": 10 + }, + { + "x": 60, + "y": 10 + }, + { + "x": 18, + "y": 11 + }, + { + "x": 20, + "y": 11 + }, + { + "x": 22, + "y": 11 + }, + { + "x": 24, + "y": 11 + }, + { + "x": 26, + "y": 11 + }, + { + "x": 28, + "y": 11 + }, + { + "x": 30, + "y": 11 + }, + { + "x": 32, + "y": 11 + }, + { + "x": 34, + "y": 11 + }, + { + "x": 36, + "y": 11 + }, + { + "x": 38, + "y": 11 + }, + { + "x": 40, + "y": 11 + }, + { + "x": 42, + "y": 11 + }, + { + "x": 44, + "y": 11 + }, + { + "x": 46, + "y": 11 + }, + { + "x": 48, + "y": 11 + }, + { + "x": 50, + "y": 11 + }, + { + "x": 52, + "y": 11 + }, + { + "x": 54, + "y": 11 + }, + { + "x": 56, + "y": 11 + }, + { + "x": 58, + "y": 11 + }, + { + "x": 60, + "y": 11 + }, + { + "x": 0, + "y": 13 + }, + { + "x": 2, + "y": 13 + }, + { + "x": 4, + "y": 13 + }, + { + "x": 6, + "y": 13 + }, + { + "x": 8, + "y": 13 + }, + { + "x": 10, + "y": 13 + }, + { + "x": 12, + "y": 13 + }, + { + "x": 14, + "y": 13 + }, + { + "x": 16, + "y": 13 + }, + { + "x": 18, + "y": 13 + }, + { + "x": 20, + "y": 13 + }, + { + "x": 22, + "y": 13 + }, + { + "x": 24, + "y": 13 + }, + { + "x": 26, + "y": 13 + }, + { + "x": 28, + "y": 13 + }, + { + "x": 30, + "y": 13 + }, + { + "x": 32, + "y": 13 + }, + { + "x": 34, + "y": 13 + }, + { + "x": 36, + "y": 13 + }, + { + "x": 38, + "y": 13 + }, + { + "x": 40, + "y": 13 + }, + { + "x": 42, + "y": 13 + }, + { + "x": 44, + "y": 13 + }, + { + "x": 46, + "y": 13 + }, + { + "x": 48, + "y": 13 + }, + { + "x": 50, + "y": 13 + }, + { + "x": 52, + "y": 13 + }, + { + "x": 54, + "y": 13 + }, + { + "x": 56, + "y": 13 + }, + { + "x": 58, + "y": 13 + }, + { + "x": 60, + "y": 13 + }, + { + "x": 62, + "y": 13 + }, + { + "x": 64, + "y": 13 + }, + { + "x": 66, + "y": 13 + }, + { + "x": 68, + "y": 13 + }, + { + "x": 70, + "y": 13 + }, + { + "x": 72, + "y": 13 + }, + { + "x": 74, + "y": 13 + }, + { + "x": 0, + "y": 14 + }, + { + "x": 2, + "y": 14 + }, + { + "x": 4, + "y": 14 + }, + { + "x": 6, + "y": 14 + }, + { + "x": 8, + "y": 14 + }, + { + "x": 10, + "y": 14 + }, + { + "x": 12, + "y": 14 + }, + { + "x": 14, + "y": 14 + }, + { + "x": 16, + "y": 14 + }, + { + "x": 18, + "y": 14 + }, + { + "x": 20, + "y": 14 + }, + { + "x": 22, + "y": 14 + }, + { + "x": 24, + "y": 14 + }, + { + "x": 26, + "y": 14 + }, + { + "x": 28, + "y": 14 + }, + { + "x": 30, + "y": 14 + }, + { + "x": 32, + "y": 14 + }, + { + "x": 34, + "y": 14 + }, + { + "x": 36, + "y": 14 + }, + { + "x": 38, + "y": 14 + }, + { + "x": 40, + "y": 14 + }, + { + "x": 42, + "y": 14 + }, + { + "x": 44, + "y": 14 + }, + { + "x": 46, + "y": 14 + }, + { + "x": 48, + "y": 14 + }, + { + "x": 50, + "y": 14 + }, + { + "x": 52, + "y": 14 + }, + { + "x": 54, + "y": 14 + }, + { + "x": 56, + "y": 14 + }, + { + "x": 58, + "y": 14 + }, + { + "x": 60, + "y": 14 + }, + { + "x": 62, + "y": 14 + }, + { + "x": 64, + "y": 14 + }, + { + "x": 66, + "y": 14 + }, + { + "x": 68, + "y": 14 + }, + { + "x": 70, + "y": 14 + }, + { + "x": 72, + "y": 14 + }, + { + "x": 74, + "y": 14 + }, + { + "x": 0, + "y": 15 + }, + { + "x": 2, + "y": 15 + }, + { + "x": 4, + "y": 15 + }, + { + "x": 6, + "y": 15 + }, + { + "x": 8, + "y": 15 + }, + { + "x": 10, + "y": 15 + }, + { + "x": 12, + "y": 15 + }, + { + "x": 14, + "y": 15 + }, + { + "x": 16, + "y": 15 + }, + { + "x": 18, + "y": 15 + }, + { + "x": 20, + "y": 15 + }, + { + "x": 22, + "y": 15 + }, + { + "x": 24, + "y": 15 + }, + { + "x": 26, + "y": 15 + }, + { + "x": 28, + "y": 15 + }, + { + "x": 30, + "y": 15 + }, + { + "x": 32, + "y": 15 + }, + { + "x": 34, + "y": 15 + }, + { + "x": 36, + "y": 15 + }, + { + "x": 38, + "y": 15 + }, + { + "x": 40, + "y": 15 + }, + { + "x": 42, + "y": 15 + }, + { + "x": 44, + "y": 15 + }, + { + "x": 46, + "y": 15 + }, + { + "x": 48, + "y": 15 + }, + { + "x": 50, + "y": 15 + }, + { + "x": 52, + "y": 15 + }, + { + "x": 54, + "y": 15 + }, + { + "x": 56, + "y": 15 + }, + { + "x": 58, + "y": 15 + }, + { + "x": 60, + "y": 15 + }, + { + "x": 62, + "y": 15 + }, + { + "x": 64, + "y": 15 + }, + { + "x": 66, + "y": 15 + }, + { + "x": 68, + "y": 15 + }, + { + "x": 70, + "y": 15 + }, + { + "x": 72, + "y": 15 + }, + { + "x": 74, + "y": 15 + }, + { + "x": 0, + "y": 16 + }, + { + "x": 2, + "y": 16 + }, + { + "x": 4, + "y": 16 + }, + { + "x": 6, + "y": 16 + }, + { + "x": 8, + "y": 16 + }, + { + "x": 10, + "y": 16 + }, + { + "x": 12, + "y": 16 + }, + { + "x": 14, + "y": 16 + }, + { + "x": 16, + "y": 16 + }, + { + "x": 18, + "y": 16 + }, + { + "x": 20, + "y": 16 + }, + { + "x": 22, + "y": 16 + }, + { + "x": 24, + "y": 16 + }, + { + "x": 26, + "y": 16 + }, + { + "x": 28, + "y": 16 + }, + { + "x": 30, + "y": 16 + }, + { + "x": 32, + "y": 16 + }, + { + "x": 34, + "y": 16 + }, + { + "x": 36, + "y": 16 + }, + { + "x": 38, + "y": 16 + }, + { + "x": 40, + "y": 16 + }, + { + "x": 42, + "y": 16 + }, + { + "x": 44, + "y": 16 + }, + { + "x": 46, + "y": 16 + }, + { + "x": 48, + "y": 16 + }, + { + "x": 50, + "y": 16 + }, + { + "x": 52, + "y": 16 + }, + { + "x": 54, + "y": 16 + }, + { + "x": 56, + "y": 16 + }, + { + "x": 58, + "y": 16 + }, + { + "x": 60, + "y": 16 + }, + { + "x": 62, + "y": 16 + }, + { + "x": 64, + "y": 16 + }, + { + "x": 66, + "y": 16 + }, + { + "x": 68, + "y": 16 + }, + { + "x": 70, + "y": 16 + }, + { + "x": 72, + "y": 16 + }, + { + "x": 74, + "y": 16 + }, + { + "x": 0, + "y": 17 + }, + { + "x": 2, + "y": 17 + }, + { + "x": 4, + "y": 17 + }, + { + "x": 6, + "y": 17 + }, + { + "x": 8, + "y": 17 + }, + { + "x": 10, + "y": 17 + }, + { + "x": 12, + "y": 17 + }, + { + "x": 14, + "y": 17 + }, + { + "x": 16, + "y": 17 + }, + { + "x": 18, + "y": 17 + }, + { + "x": 20, + "y": 17 + }, + { + "x": 22, + "y": 17 + }, + { + "x": 24, + "y": 17 + }, + { + "x": 26, + "y": 17 + }, + { + "x": 28, + "y": 17 + }, + { + "x": 30, + "y": 17 + }, + { + "x": 32, + "y": 17 + }, + { + "x": 34, + "y": 17 + }, + { + "x": 36, + "y": 17 + }, + { + "x": 38, + "y": 17 + }, + { + "x": 40, + "y": 17 + }, + { + "x": 42, + "y": 17 + }, + { + "x": 44, + "y": 17 + }, + { + "x": 46, + "y": 17 + }, + { + "x": 48, + "y": 17 + }, + { + "x": 50, + "y": 17 + }, + { + "x": 52, + "y": 17 + }, + { + "x": 54, + "y": 17 + }, + { + "x": 56, + "y": 17 + }, + { + "x": 58, + "y": 17 + }, + { + "x": 60, + "y": 17 + }, + { + "x": 62, + "y": 17 + }, + { + "x": 64, + "y": 17 + }, + { + "x": 66, + "y": 17 + }, + { + "x": 68, + "y": 17 + }, + { + "x": 70, + "y": 17 + }, + { + "x": 72, + "y": 17 + }, + { + "x": 74, + "y": 17 + }, + { + "x": 0, + "y": 18 + }, + { + "x": 2, + "y": 18 + }, + { + "x": 4, + "y": 18 + }, + { + "x": 6, + "y": 18 + }, + { + "x": 8, + "y": 18 + }, + { + "x": 10, + "y": 18 + }, + { + "x": 12, + "y": 18 + }, + { + "x": 14, + "y": 18 + }, + { + "x": 16, + "y": 18 + }, + { + "x": 18, + "y": 18 + }, + { + "x": 20, + "y": 18 + }, + { + "x": 22, + "y": 18 + }, + { + "x": 24, + "y": 18 + }, + { + "x": 26, + "y": 18 + }, + { + "x": 28, + "y": 18 + }, + { + "x": 30, + "y": 18 + }, + { + "x": 32, + "y": 18 + }, + { + "x": 34, + "y": 18 + }, + { + "x": 36, + "y": 18 + }, + { + "x": 38, + "y": 18 + }, + { + "x": 40, + "y": 18 + }, + { + "x": 42, + "y": 18 + }, + { + "x": 44, + "y": 18 + }, + { + "x": 46, + "y": 18 + }, + { + "x": 48, + "y": 18 + }, + { + "x": 50, + "y": 18 + }, + { + "x": 52, + "y": 18 + }, + { + "x": 54, + "y": 18 + }, + { + "x": 56, + "y": 18 + }, + { + "x": 58, + "y": 18 + }, + { + "x": 60, + "y": 18 + }, + { + "x": 62, + "y": 18 + }, + { + "x": 64, + "y": 18 + }, + { + "x": 66, + "y": 18 + }, + { + "x": 68, + "y": 18 + }, + { + "x": 70, + "y": 18 + }, + { + "x": 72, + "y": 18 + }, + { + "x": 74, + "y": 18 + }, + { + "x": 0, + "y": 19 + }, + { + "x": 2, + "y": 19 + }, + { + "x": 4, + "y": 19 + }, + { + "x": 6, + "y": 19 + }, + { + "x": 8, + "y": 19 + }, + { + "x": 10, + "y": 19 + }, + { + "x": 12, + "y": 19 + }, + { + "x": 14, + "y": 19 + }, + { + "x": 16, + "y": 19 + }, + { + "x": 18, + "y": 19 + }, + { + "x": 20, + "y": 19 + }, + { + "x": 22, + "y": 19 + }, + { + "x": 24, + "y": 19 + }, + { + "x": 26, + "y": 19 + }, + { + "x": 28, + "y": 19 + }, + { + "x": 30, + "y": 19 + }, + { + "x": 32, + "y": 19 + }, + { + "x": 34, + "y": 19 + }, + { + "x": 36, + "y": 19 + }, + { + "x": 38, + "y": 19 + }, + { + "x": 40, + "y": 19 + }, + { + "x": 42, + "y": 19 + }, + { + "x": 44, + "y": 19 + }, + { + "x": 46, + "y": 19 + }, + { + "x": 48, + "y": 19 + }, + { + "x": 50, + "y": 19 + }, + { + "x": 52, + "y": 19 + }, + { + "x": 54, + "y": 19 + }, + { + "x": 56, + "y": 19 + }, + { + "x": 58, + "y": 19 + }, + { + "x": 60, + "y": 19 + }, + { + "x": 62, + "y": 19 + }, + { + "x": 64, + "y": 19 + }, + { + "x": 66, + "y": 19 + }, + { + "x": 68, + "y": 19 + }, + { + "x": 70, + "y": 19 + }, + { + "x": 72, + "y": 19 + }, + { + "x": 74, + "y": 19 + }, + { + "x": 0, + "y": 20 + }, + { + "x": 2, + "y": 20 + }, + { + "x": 4, + "y": 20 + }, + { + "x": 6, + "y": 20 + }, + { + "x": 8, + "y": 20 + }, + { + "x": 10, + "y": 20 + }, + { + "x": 12, + "y": 20 + }, + { + "x": 14, + "y": 20 + }, + { + "x": 16, + "y": 20 + }, + { + "x": 18, + "y": 20 + }, + { + "x": 20, + "y": 20 + }, + { + "x": 22, + "y": 20 + }, + { + "x": 24, + "y": 20 + }, + { + "x": 26, + "y": 20 + }, + { + "x": 28, + "y": 20 + }, + { + "x": 30, + "y": 20 + }, + { + "x": 32, + "y": 20 + }, + { + "x": 34, + "y": 20 + }, + { + "x": 36, + "y": 20 + }, + { + "x": 38, + "y": 20 + }, + { + "x": 40, + "y": 20 + }, + { + "x": 42, + "y": 20 + }, + { + "x": 44, + "y": 20 + }, + { + "x": 46, + "y": 20 + }, + { + "x": 48, + "y": 20 + }, + { + "x": 50, + "y": 20 + }, + { + "x": 52, + "y": 20 + }, + { + "x": 54, + "y": 20 + }, + { + "x": 56, + "y": 20 + }, + { + "x": 58, + "y": 20 + }, + { + "x": 60, + "y": 20 + }, + { + "x": 62, + "y": 20 + }, + { + "x": 64, + "y": 20 + }, + { + "x": 66, + "y": 20 + }, + { + "x": 68, + "y": 20 + }, + { + "x": 70, + "y": 20 + }, + { + "x": 72, + "y": 20 + }, + { + "x": 74, + "y": 20 + }, + { + "x": 0, + "y": 21 + }, + { + "x": 2, + "y": 21 + }, + { + "x": 4, + "y": 21 + }, + { + "x": 6, + "y": 21 + }, + { + "x": 8, + "y": 21 + }, + { + "x": 10, + "y": 21 + }, + { + "x": 12, + "y": 21 + }, + { + "x": 14, + "y": 21 + }, + { + "x": 16, + "y": 21 + }, + { + "x": 18, + "y": 21 + }, + { + "x": 20, + "y": 21 + }, + { + "x": 22, + "y": 21 + }, + { + "x": 24, + "y": 21 + }, + { + "x": 26, + "y": 21 + }, + { + "x": 28, + "y": 21 + }, + { + "x": 30, + "y": 21 + }, + { + "x": 32, + "y": 21 + }, + { + "x": 34, + "y": 21 + }, + { + "x": 36, + "y": 21 + }, + { + "x": 38, + "y": 21 + }, + { + "x": 40, + "y": 21 + }, + { + "x": 42, + "y": 21 + }, + { + "x": 44, + "y": 21 + }, + { + "x": 46, + "y": 21 + }, + { + "x": 48, + "y": 21 + }, + { + "x": 50, + "y": 21 + }, + { + "x": 52, + "y": 21 + }, + { + "x": 54, + "y": 21 + }, + { + "x": 56, + "y": 21 + }, + { + "x": 58, + "y": 21 + }, + { + "x": 60, + "y": 21 + }, + { + "x": 62, + "y": 21 + }, + { + "x": 64, + "y": 21 + }, + { + "x": 66, + "y": 21 + }, + { + "x": 68, + "y": 21 + }, + { + "x": 70, + "y": 21 + }, + { + "x": 72, + "y": 21 + }, + { + "x": 74, + "y": 21 + }, + { + "x": 0, + "y": 23 + }, + { + "x": 2, + "y": 23 + }, + { + "x": 4, + "y": 23 + }, + { + "x": 6, + "y": 23 + }, + { + "x": 8, + "y": 23 + }, + { + "x": 10, + "y": 23 + }, + { + "x": 12, + "y": 23 + }, + { + "x": 14, + "y": 23 + }, + { + "x": 16, + "y": 23 + }, + { + "x": 18, + "y": 23 + }, + { + "x": 20, + "y": 23 + }, + { + "x": 22, + "y": 23 + }, + { + "x": 24, + "y": 23 + }, + { + "x": 26, + "y": 23 + }, + { + "x": 28, + "y": 23 + }, + { + "x": 30, + "y": 23 + }, + { + "x": 32, + "y": 23 + }, + { + "x": 34, + "y": 23 + }, + { + "x": 36, + "y": 23 + }, + { + "x": 38, + "y": 23 + }, + { + "x": 40, + "y": 23 + }, + { + "x": 42, + "y": 23 + }, + { + "x": 44, + "y": 23 + }, + { + "x": 46, + "y": 23 + }, + { + "x": 48, + "y": 23 + }, + { + "x": 50, + "y": 23 + }, + { + "x": 52, + "y": 23 + }, + { + "x": 54, + "y": 23 + }, + { + "x": 56, + "y": 23 + }, + { + "x": 58, + "y": 23 + }, + { + "x": 60, + "y": 23 + }, + { + "x": 62, + "y": 23 + }, + { + "x": 64, + "y": 23 + }, + { + "x": 66, + "y": 23 + }, + { + "x": 68, + "y": 23 + }, + { + "x": 70, + "y": 23 + }, + { + "x": 72, + "y": 23 + }, + { + "x": 74, + "y": 23 + }, + { + "x": 0, + "y": 24 + }, + { + "x": 2, + "y": 24 + }, + { + "x": 4, + "y": 24 + }, + { + "x": 6, + "y": 24 + }, + { + "x": 8, + "y": 24 + }, + { + "x": 10, + "y": 24 + }, + { + "x": 12, + "y": 24 + }, + { + "x": 14, + "y": 24 + }, + { + "x": 16, + "y": 24 + }, + { + "x": 18, + "y": 24 + }, + { + "x": 20, + "y": 24 + }, + { + "x": 22, + "y": 24 + }, + { + "x": 24, + "y": 24 + }, + { + "x": 26, + "y": 24 + }, + { + "x": 28, + "y": 24 + }, + { + "x": 30, + "y": 24 + }, + { + "x": 32, + "y": 24 + }, + { + "x": 34, + "y": 24 + }, + { + "x": 36, + "y": 24 + }, + { + "x": 38, + "y": 24 + }, + { + "x": 40, + "y": 24 + }, + { + "x": 42, + "y": 24 + }, + { + "x": 44, + "y": 24 + }, + { + "x": 46, + "y": 24 + }, + { + "x": 48, + "y": 24 + }, + { + "x": 50, + "y": 24 + }, + { + "x": 52, + "y": 24 + }, + { + "x": 54, + "y": 24 + }, + { + "x": 56, + "y": 24 + }, + { + "x": 58, + "y": 24 + }, + { + "x": 60, + "y": 24 + }, + { + "x": 62, + "y": 24 + }, + { + "x": 64, + "y": 24 + }, + { + "x": 66, + "y": 24 + }, + { + "x": 68, + "y": 24 + }, + { + "x": 70, + "y": 24 + }, + { + "x": 72, + "y": 24 + }, + { + "x": 74, + "y": 24 + }, + { + "x": 0, + "y": 25 + }, + { + "x": 2, + "y": 25 + }, + { + "x": 4, + "y": 25 + }, + { + "x": 6, + "y": 25 + }, + { + "x": 8, + "y": 25 + }, + { + "x": 10, + "y": 25 + }, + { + "x": 12, + "y": 25 + }, + { + "x": 14, + "y": 25 + }, + { + "x": 16, + "y": 25 + }, + { + "x": 18, + "y": 25 + }, + { + "x": 20, + "y": 25 + }, + { + "x": 22, + "y": 25 + }, + { + "x": 24, + "y": 25 + }, + { + "x": 26, + "y": 25 + }, + { + "x": 28, + "y": 25 + }, + { + "x": 30, + "y": 25 + }, + { + "x": 32, + "y": 25 + }, + { + "x": 34, + "y": 25 + }, + { + "x": 36, + "y": 25 + }, + { + "x": 38, + "y": 25 + }, + { + "x": 40, + "y": 25 + }, + { + "x": 42, + "y": 25 + }, + { + "x": 44, + "y": 25 + }, + { + "x": 46, + "y": 25 + }, + { + "x": 48, + "y": 25 + }, + { + "x": 50, + "y": 25 + }, + { + "x": 52, + "y": 25 + }, + { + "x": 54, + "y": 25 + }, + { + "x": 56, + "y": 25 + }, + { + "x": 58, + "y": 25 + }, + { + "x": 60, + "y": 25 + }, + { + "x": 62, + "y": 25 + }, + { + "x": 64, + "y": 25 + }, + { + "x": 66, + "y": 25 + }, + { + "x": 68, + "y": 25 + }, + { + "x": 70, + "y": 25 + }, + { + "x": 72, + "y": 25 + }, + { + "x": 74, + "y": 25 + }, + { + "x": 0, + "y": 26 + }, + { + "x": 2, + "y": 26 + }, + { + "x": 4, + "y": 26 + }, + { + "x": 6, + "y": 26 + }, + { + "x": 8, + "y": 26 + }, + { + "x": 10, + "y": 26 + }, + { + "x": 12, + "y": 26 + }, + { + "x": 14, + "y": 26 + }, + { + "x": 16, + "y": 26 + }, + { + "x": 18, + "y": 26 + }, + { + "x": 20, + "y": 26 + }, + { + "x": 22, + "y": 26 + }, + { + "x": 24, + "y": 26 + }, + { + "x": 26, + "y": 26 + }, + { + "x": 28, + "y": 26 + }, + { + "x": 30, + "y": 26 + }, + { + "x": 32, + "y": 26 + }, + { + "x": 34, + "y": 26 + }, + { + "x": 36, + "y": 26 + }, + { + "x": 38, + "y": 26 + }, + { + "x": 40, + "y": 26 + }, + { + "x": 42, + "y": 26 + }, + { + "x": 44, + "y": 26 + }, + { + "x": 46, + "y": 26 + }, + { + "x": 48, + "y": 26 + }, + { + "x": 50, + "y": 26 + }, + { + "x": 52, + "y": 26 + }, + { + "x": 54, + "y": 26 + }, + { + "x": 56, + "y": 26 + }, + { + "x": 58, + "y": 26 + }, + { + "x": 60, + "y": 26 + }, + { + "x": 62, + "y": 26 + }, + { + "x": 64, + "y": 26 + }, + { + "x": 66, + "y": 26 + }, + { + "x": 68, + "y": 26 + }, + { + "x": 70, + "y": 26 + }, + { + "x": 72, + "y": 26 + }, + { + "x": 74, + "y": 26 + }, + { + "x": 0, + "y": 27 + }, + { + "x": 2, + "y": 27 + }, + { + "x": 4, + "y": 27 + }, + { + "x": 6, + "y": 27 + }, + { + "x": 8, + "y": 27 + }, + { + "x": 10, + "y": 27 + }, + { + "x": 12, + "y": 27 + }, + { + "x": 14, + "y": 27 + }, + { + "x": 16, + "y": 27 + }, + { + "x": 18, + "y": 27 + }, + { + "x": 20, + "y": 27 + }, + { + "x": 22, + "y": 27 + }, + { + "x": 24, + "y": 27 + }, + { + "x": 26, + "y": 27 + }, + { + "x": 28, + "y": 27 + }, + { + "x": 30, + "y": 27 + }, + { + "x": 32, + "y": 27 + }, + { + "x": 34, + "y": 27 + }, + { + "x": 36, + "y": 27 + }, + { + "x": 38, + "y": 27 + }, + { + "x": 40, + "y": 27 + }, + { + "x": 42, + "y": 27 + }, + { + "x": 44, + "y": 27 + }, + { + "x": 46, + "y": 27 + }, + { + "x": 48, + "y": 27 + }, + { + "x": 50, + "y": 27 + }, + { + "x": 52, + "y": 27 + }, + { + "x": 54, + "y": 27 + }, + { + "x": 56, + "y": 27 + }, + { + "x": 58, + "y": 27 + }, + { + "x": 60, + "y": 27 + }, + { + "x": 62, + "y": 27 + }, + { + "x": 64, + "y": 27 + }, + { + "x": 66, + "y": 27 + }, + { + "x": 68, + "y": 27 + }, + { + "x": 70, + "y": 27 + }, + { + "x": 72, + "y": 27 + }, + { + "x": 74, + "y": 27 + }, + { + "x": 0, + "y": 28 + }, + { + "x": 2, + "y": 28 + }, + { + "x": 4, + "y": 28 + }, + { + "x": 6, + "y": 28 + }, + { + "x": 8, + "y": 28 + }, + { + "x": 10, + "y": 28 + }, + { + "x": 12, + "y": 28 + }, + { + "x": 14, + "y": 28 + }, + { + "x": 16, + "y": 28 + }, + { + "x": 18, + "y": 28 + }, + { + "x": 20, + "y": 28 + }, + { + "x": 22, + "y": 28 + }, + { + "x": 24, + "y": 28 + }, + { + "x": 26, + "y": 28 + }, + { + "x": 28, + "y": 28 + }, + { + "x": 30, + "y": 28 + }, + { + "x": 32, + "y": 28 + }, + { + "x": 34, + "y": 28 + }, + { + "x": 36, + "y": 28 + }, + { + "x": 38, + "y": 28 + }, + { + "x": 40, + "y": 28 + }, + { + "x": 42, + "y": 28 + }, + { + "x": 44, + "y": 28 + }, + { + "x": 46, + "y": 28 + }, + { + "x": 48, + "y": 28 + }, + { + "x": 50, + "y": 28 + }, + { + "x": 52, + "y": 28 + }, + { + "x": 54, + "y": 28 + }, + { + "x": 56, + "y": 28 + }, + { + "x": 58, + "y": 28 + }, + { + "x": 60, + "y": 28 + }, + { + "x": 62, + "y": 28 + }, + { + "x": 64, + "y": 28 + }, + { + "x": 66, + "y": 28 + }, + { + "x": 68, + "y": 28 + }, + { + "x": 70, + "y": 28 + }, + { + "x": 72, + "y": 28 + }, + { + "x": 74, + "y": 28 + }, + { + "x": 0, + "y": 29 + }, + { + "x": 2, + "y": 29 + }, + { + "x": 4, + "y": 29 + }, + { + "x": 6, + "y": 29 + }, + { + "x": 8, + "y": 29 + }, + { + "x": 10, + "y": 29 + }, + { + "x": 12, + "y": 29 + }, + { + "x": 14, + "y": 29 + }, + { + "x": 16, + "y": 29 + }, + { + "x": 18, + "y": 29 + }, + { + "x": 20, + "y": 29 + }, + { + "x": 22, + "y": 29 + }, + { + "x": 24, + "y": 29 + }, + { + "x": 26, + "y": 29 + }, + { + "x": 28, + "y": 29 + }, + { + "x": 30, + "y": 29 + }, + { + "x": 32, + "y": 29 + }, + { + "x": 34, + "y": 29 + }, + { + "x": 36, + "y": 29 + }, + { + "x": 38, + "y": 29 + }, + { + "x": 40, + "y": 29 + }, + { + "x": 42, + "y": 29 + }, + { + "x": 44, + "y": 29 + }, + { + "x": 46, + "y": 29 + }, + { + "x": 48, + "y": 29 + }, + { + "x": 50, + "y": 29 + }, + { + "x": 52, + "y": 29 + }, + { + "x": 54, + "y": 29 + }, + { + "x": 56, + "y": 29 + }, + { + "x": 58, + "y": 29 + }, + { + "x": 60, + "y": 29 + }, + { + "x": 62, + "y": 29 + }, + { + "x": 64, + "y": 29 + }, + { + "x": 66, + "y": 29 + }, + { + "x": 68, + "y": 29 + }, + { + "x": 70, + "y": 29 + }, + { + "x": 72, + "y": 29 + }, + { + "x": 74, + "y": 29 + }, + { + "x": 0, + "y": 30 + }, + { + "x": 2, + "y": 30 + }, + { + "x": 4, + "y": 30 + }, + { + "x": 6, + "y": 30 + }, + { + "x": 8, + "y": 30 + }, + { + "x": 10, + "y": 30 + }, + { + "x": 12, + "y": 30 + }, + { + "x": 14, + "y": 30 + }, + { + "x": 16, + "y": 30 + }, + { + "x": 18, + "y": 30 + }, + { + "x": 20, + "y": 30 + }, + { + "x": 22, + "y": 30 + }, + { + "x": 24, + "y": 30 + }, + { + "x": 26, + "y": 30 + }, + { + "x": 28, + "y": 30 + }, + { + "x": 30, + "y": 30 + }, + { + "x": 32, + "y": 30 + }, + { + "x": 34, + "y": 30 + }, + { + "x": 36, + "y": 30 + }, + { + "x": 38, + "y": 30 + }, + { + "x": 40, + "y": 30 + }, + { + "x": 42, + "y": 30 + }, + { + "x": 44, + "y": 30 + }, + { + "x": 46, + "y": 30 + }, + { + "x": 48, + "y": 30 + }, + { + "x": 50, + "y": 30 + }, + { + "x": 52, + "y": 30 + }, + { + "x": 54, + "y": 30 + }, + { + "x": 56, + "y": 30 + }, + { + "x": 58, + "y": 30 + }, + { + "x": 60, + "y": 30 + }, + { + "x": 62, + "y": 30 + }, + { + "x": 64, + "y": 30 + }, + { + "x": 66, + "y": 30 + }, + { + "x": 68, + "y": 30 + }, + { + "x": 70, + "y": 30 + }, + { + "x": 72, + "y": 30 + }, + { + "x": 74, + "y": 30 + }, + { + "x": 0, + "y": 31 + }, + { + "x": 2, + "y": 31 + }, + { + "x": 4, + "y": 31 + }, + { + "x": 6, + "y": 31 + }, + { + "x": 8, + "y": 31 + }, + { + "x": 10, + "y": 31 + }, + { + "x": 12, + "y": 31 + }, + { + "x": 14, + "y": 31 + }, + { + "x": 16, + "y": 31 + }, + { + "x": 18, + "y": 31 + }, + { + "x": 20, + "y": 31 + }, + { + "x": 22, + "y": 31 + }, + { + "x": 24, + "y": 31 + }, + { + "x": 26, + "y": 31 + }, + { + "x": 28, + "y": 31 + }, + { + "x": 30, + "y": 31 + }, + { + "x": 32, + "y": 31 + }, + { + "x": 34, + "y": 31 + }, + { + "x": 36, + "y": 31 + }, + { + "x": 38, + "y": 31 + }, + { + "x": 40, + "y": 31 + }, + { + "x": 42, + "y": 31 + }, + { + "x": 44, + "y": 31 + }, + { + "x": 46, + "y": 31 + }, + { + "x": 48, + "y": 31 + }, + { + "x": 50, + "y": 31 + }, + { + "x": 52, + "y": 31 + }, + { + "x": 54, + "y": 31 + }, + { + "x": 56, + "y": 31 + }, + { + "x": 58, + "y": 31 + }, + { + "x": 60, + "y": 31 + }, + { + "x": 62, + "y": 31 + }, + { + "x": 64, + "y": 31 + }, + { + "x": 66, + "y": 31 + }, + { + "x": 68, + "y": 31 + }, + { + "x": 70, + "y": 31 + }, + { + "x": 72, + "y": 31 + }, + { + "x": 74, + "y": 31 + }, + { + "x": 0, + "y": 33 + }, + { + "x": 2, + "y": 33 + }, + { + "x": 4, + "y": 33 + }, + { + "x": 6, + "y": 33 + }, + { + "x": 8, + "y": 33 + }, + { + "x": 10, + "y": 33 + }, + { + "x": 12, + "y": 33 + }, + { + "x": 14, + "y": 33 + }, + { + "x": 16, + "y": 33 + }, + { + "x": 18, + "y": 33 + }, + { + "x": 20, + "y": 33 + }, + { + "x": 22, + "y": 33 + }, + { + "x": 24, + "y": 33 + }, + { + "x": 26, + "y": 33 + }, + { + "x": 28, + "y": 33 + }, + { + "x": 30, + "y": 33 + }, + { + "x": 32, + "y": 33 + }, + { + "x": 34, + "y": 33 + }, + { + "x": 36, + "y": 33 + }, + { + "x": 38, + "y": 33 + }, + { + "x": 40, + "y": 33 + }, + { + "x": 42, + "y": 33 + }, + { + "x": 44, + "y": 33 + }, + { + "x": 46, + "y": 33 + }, + { + "x": 48, + "y": 33 + }, + { + "x": 50, + "y": 33 + }, + { + "x": 52, + "y": 33 + }, + { + "x": 54, + "y": 33 + }, + { + "x": 56, + "y": 33 + }, + { + "x": 58, + "y": 33 + }, + { + "x": 60, + "y": 33 + }, + { + "x": 62, + "y": 33 + }, + { + "x": 64, + "y": 33 + }, + { + "x": 66, + "y": 33 + }, + { + "x": 68, + "y": 33 + }, + { + "x": 70, + "y": 33 + }, + { + "x": 72, + "y": 33 + }, + { + "x": 74, + "y": 33 + }, + { + "x": 0, + "y": 34 + }, + { + "x": 2, + "y": 34 + }, + { + "x": 4, + "y": 34 + }, + { + "x": 6, + "y": 34 + }, + { + "x": 8, + "y": 34 + }, + { + "x": 10, + "y": 34 + }, + { + "x": 12, + "y": 34 + }, + { + "x": 14, + "y": 34 + }, + { + "x": 16, + "y": 34 + }, + { + "x": 18, + "y": 34 + }, + { + "x": 20, + "y": 34 + }, + { + "x": 22, + "y": 34 + }, + { + "x": 24, + "y": 34 + }, + { + "x": 26, + "y": 34 + }, + { + "x": 28, + "y": 34 + }, + { + "x": 30, + "y": 34 + }, + { + "x": 32, + "y": 34 + }, + { + "x": 34, + "y": 34 + }, + { + "x": 36, + "y": 34 + }, + { + "x": 38, + "y": 34 + }, + { + "x": 40, + "y": 34 + }, + { + "x": 42, + "y": 34 + }, + { + "x": 44, + "y": 34 + }, + { + "x": 46, + "y": 34 + }, + { + "x": 48, + "y": 34 + }, + { + "x": 50, + "y": 34 + }, + { + "x": 52, + "y": 34 + }, + { + "x": 54, + "y": 34 + }, + { + "x": 56, + "y": 34 + }, + { + "x": 58, + "y": 34 + }, + { + "x": 60, + "y": 34 + }, + { + "x": 62, + "y": 34 + }, + { + "x": 64, + "y": 34 + }, + { + "x": 66, + "y": 34 + }, + { + "x": 68, + "y": 34 + }, + { + "x": 70, + "y": 34 + }, + { + "x": 72, + "y": 34 + }, + { + "x": 74, + "y": 34 + }, + { + "x": 0, + "y": 35 + }, + { + "x": 2, + "y": 35 + }, + { + "x": 4, + "y": 35 + }, + { + "x": 6, + "y": 35 + }, + { + "x": 8, + "y": 35 + }, + { + "x": 10, + "y": 35 + }, + { + "x": 12, + "y": 35 + }, + { + "x": 14, + "y": 35 + }, + { + "x": 16, + "y": 35 + }, + { + "x": 18, + "y": 35 + }, + { + "x": 20, + "y": 35 + }, + { + "x": 22, + "y": 35 + }, + { + "x": 24, + "y": 35 + }, + { + "x": 26, + "y": 35 + }, + { + "x": 28, + "y": 35 + }, + { + "x": 30, + "y": 35 + }, + { + "x": 32, + "y": 35 + }, + { + "x": 34, + "y": 35 + }, + { + "x": 36, + "y": 35 + }, + { + "x": 38, + "y": 35 + }, + { + "x": 40, + "y": 35 + }, + { + "x": 42, + "y": 35 + }, + { + "x": 44, + "y": 35 + }, + { + "x": 46, + "y": 35 + }, + { + "x": 48, + "y": 35 + }, + { + "x": 50, + "y": 35 + }, + { + "x": 52, + "y": 35 + }, + { + "x": 54, + "y": 35 + }, + { + "x": 56, + "y": 35 + }, + { + "x": 58, + "y": 35 + }, + { + "x": 60, + "y": 35 + }, + { + "x": 62, + "y": 35 + }, + { + "x": 64, + "y": 35 + }, + { + "x": 66, + "y": 35 + }, + { + "x": 68, + "y": 35 + }, + { + "x": 70, + "y": 35 + }, + { + "x": 72, + "y": 35 + }, + { + "x": 74, + "y": 35 + }, + { + "x": 0, + "y": 36 + }, + { + "x": 2, + "y": 36 + }, + { + "x": 4, + "y": 36 + }, + { + "x": 6, + "y": 36 + }, + { + "x": 8, + "y": 36 + }, + { + "x": 10, + "y": 36 + }, + { + "x": 12, + "y": 36 + }, + { + "x": 14, + "y": 36 + }, + { + "x": 16, + "y": 36 + }, + { + "x": 18, + "y": 36 + }, + { + "x": 20, + "y": 36 + }, + { + "x": 22, + "y": 36 + }, + { + "x": 24, + "y": 36 + }, + { + "x": 26, + "y": 36 + }, + { + "x": 28, + "y": 36 + }, + { + "x": 30, + "y": 36 + }, + { + "x": 32, + "y": 36 + }, + { + "x": 34, + "y": 36 + }, + { + "x": 36, + "y": 36 + }, + { + "x": 38, + "y": 36 + }, + { + "x": 40, + "y": 36 + }, + { + "x": 42, + "y": 36 + }, + { + "x": 44, + "y": 36 + }, + { + "x": 46, + "y": 36 + }, + { + "x": 48, + "y": 36 + }, + { + "x": 50, + "y": 36 + }, + { + "x": 52, + "y": 36 + }, + { + "x": 54, + "y": 36 + }, + { + "x": 56, + "y": 36 + }, + { + "x": 58, + "y": 36 + }, + { + "x": 60, + "y": 36 + }, + { + "x": 62, + "y": 36 + }, + { + "x": 64, + "y": 36 + }, + { + "x": 66, + "y": 36 + }, + { + "x": 68, + "y": 36 + }, + { + "x": 70, + "y": 36 + }, + { + "x": 72, + "y": 36 + }, + { + "x": 74, + "y": 36 + }, + { + "x": 0, + "y": 37 + }, + { + "x": 2, + "y": 37 + }, + { + "x": 4, + "y": 37 + }, + { + "x": 6, + "y": 37 + }, + { + "x": 8, + "y": 37 + }, + { + "x": 10, + "y": 37 + }, + { + "x": 12, + "y": 37 + }, + { + "x": 14, + "y": 37 + }, + { + "x": 16, + "y": 37 + }, + { + "x": 18, + "y": 37 + }, + { + "x": 20, + "y": 37 + }, + { + "x": 22, + "y": 37 + }, + { + "x": 24, + "y": 37 + }, + { + "x": 26, + "y": 37 + }, + { + "x": 28, + "y": 37 + }, + { + "x": 30, + "y": 37 + }, + { + "x": 32, + "y": 37 + }, + { + "x": 34, + "y": 37 + }, + { + "x": 36, + "y": 37 + }, + { + "x": 38, + "y": 37 + }, + { + "x": 40, + "y": 37 + }, + { + "x": 42, + "y": 37 + }, + { + "x": 44, + "y": 37 + }, + { + "x": 46, + "y": 37 + }, + { + "x": 48, + "y": 37 + }, + { + "x": 50, + "y": 37 + }, + { + "x": 52, + "y": 37 + }, + { + "x": 54, + "y": 37 + }, + { + "x": 56, + "y": 37 + }, + { + "x": 58, + "y": 37 + }, + { + "x": 60, + "y": 37 + }, + { + "x": 62, + "y": 37 + }, + { + "x": 64, + "y": 37 + }, + { + "x": 66, + "y": 37 + }, + { + "x": 68, + "y": 37 + }, + { + "x": 70, + "y": 37 + }, + { + "x": 72, + "y": 37 + }, + { + "x": 74, + "y": 37 + }, + { + "x": 0, + "y": 38 + }, + { + "x": 2, + "y": 38 + }, + { + "x": 4, + "y": 38 + }, + { + "x": 6, + "y": 38 + }, + { + "x": 8, + "y": 38 + }, + { + "x": 10, + "y": 38 + }, + { + "x": 12, + "y": 38 + }, + { + "x": 14, + "y": 38 + }, + { + "x": 16, + "y": 38 + }, + { + "x": 18, + "y": 38 + }, + { + "x": 20, + "y": 38 + }, + { + "x": 22, + "y": 38 + }, + { + "x": 24, + "y": 38 + }, + { + "x": 26, + "y": 38 + }, + { + "x": 28, + "y": 38 + }, + { + "x": 30, + "y": 38 + }, + { + "x": 32, + "y": 38 + }, + { + "x": 34, + "y": 38 + }, + { + "x": 36, + "y": 38 + }, + { + "x": 38, + "y": 38 + }, + { + "x": 40, + "y": 38 + }, + { + "x": 42, + "y": 38 + }, + { + "x": 44, + "y": 38 + }, + { + "x": 46, + "y": 38 + }, + { + "x": 48, + "y": 38 + }, + { + "x": 50, + "y": 38 + }, + { + "x": 52, + "y": 38 + }, + { + "x": 54, + "y": 38 + }, + { + "x": 56, + "y": 38 + }, + { + "x": 58, + "y": 38 + }, + { + "x": 60, + "y": 38 + }, + { + "x": 62, + "y": 38 + }, + { + "x": 64, + "y": 38 + }, + { + "x": 66, + "y": 38 + }, + { + "x": 68, + "y": 38 + }, + { + "x": 70, + "y": 38 + }, + { + "x": 72, + "y": 38 + }, + { + "x": 74, + "y": 38 + }, + { + "x": 0, + "y": 39 + }, + { + "x": 2, + "y": 39 + }, + { + "x": 4, + "y": 39 + }, + { + "x": 6, + "y": 39 + }, + { + "x": 8, + "y": 39 + }, + { + "x": 10, + "y": 39 + }, + { + "x": 12, + "y": 39 + }, + { + "x": 14, + "y": 39 + }, + { + "x": 16, + "y": 39 + }, + { + "x": 18, + "y": 39 + }, + { + "x": 20, + "y": 39 + }, + { + "x": 22, + "y": 39 + }, + { + "x": 24, + "y": 39 + }, + { + "x": 26, + "y": 39 + }, + { + "x": 28, + "y": 39 + }, + { + "x": 30, + "y": 39 + }, + { + "x": 32, + "y": 39 + }, + { + "x": 34, + "y": 39 + }, + { + "x": 36, + "y": 39 + }, + { + "x": 38, + "y": 39 + }, + { + "x": 40, + "y": 39 + }, + { + "x": 42, + "y": 39 + }, + { + "x": 44, + "y": 39 + }, + { + "x": 46, + "y": 39 + }, + { + "x": 48, + "y": 39 + }, + { + "x": 50, + "y": 39 + }, + { + "x": 52, + "y": 39 + }, + { + "x": 54, + "y": 39 + }, + { + "x": 56, + "y": 39 + }, + { + "x": 58, + "y": 39 + }, + { + "x": 60, + "y": 39 + }, + { + "x": 62, + "y": 39 + }, + { + "x": 64, + "y": 39 + }, + { + "x": 66, + "y": 39 + }, + { + "x": 68, + "y": 39 + }, + { + "x": 70, + "y": 39 + }, + { + "x": 72, + "y": 39 + }, + { + "x": 74, + "y": 39 + }, + { + "x": 0, + "y": 40 + }, + { + "x": 2, + "y": 40 + }, + { + "x": 4, + "y": 40 + }, + { + "x": 6, + "y": 40 + }, + { + "x": 8, + "y": 40 + }, + { + "x": 10, + "y": 40 + }, + { + "x": 12, + "y": 40 + }, + { + "x": 14, + "y": 40 + }, + { + "x": 16, + "y": 40 + }, + { + "x": 18, + "y": 40 + }, + { + "x": 20, + "y": 40 + }, + { + "x": 22, + "y": 40 + }, + { + "x": 24, + "y": 40 + }, + { + "x": 26, + "y": 40 + }, + { + "x": 28, + "y": 40 + }, + { + "x": 30, + "y": 40 + }, + { + "x": 32, + "y": 40 + }, + { + "x": 34, + "y": 40 + }, + { + "x": 36, + "y": 40 + }, + { + "x": 38, + "y": 40 + }, + { + "x": 40, + "y": 40 + }, + { + "x": 42, + "y": 40 + }, + { + "x": 44, + "y": 40 + }, + { + "x": 46, + "y": 40 + }, + { + "x": 48, + "y": 40 + }, + { + "x": 50, + "y": 40 + }, + { + "x": 52, + "y": 40 + }, + { + "x": 54, + "y": 40 + }, + { + "x": 56, + "y": 40 + }, + { + "x": 58, + "y": 40 + }, + { + "x": 60, + "y": 40 + }, + { + "x": 62, + "y": 40 + }, + { + "x": 64, + "y": 40 + }, + { + "x": 66, + "y": 40 + }, + { + "x": 68, + "y": 40 + }, + { + "x": 70, + "y": 40 + }, + { + "x": 72, + "y": 40 + }, + { + "x": 74, + "y": 40 + }, + { + "x": 0, + "y": 41 + }, + { + "x": 2, + "y": 41 + }, + { + "x": 4, + "y": 41 + }, + { + "x": 6, + "y": 41 + }, + { + "x": 8, + "y": 41 + }, + { + "x": 10, + "y": 41 + }, + { + "x": 12, + "y": 41 + }, + { + "x": 14, + "y": 41 + }, + { + "x": 16, + "y": 41 + }, + { + "x": 18, + "y": 41 + }, + { + "x": 20, + "y": 41 + }, + { + "x": 22, + "y": 41 + }, + { + "x": 24, + "y": 41 + }, + { + "x": 26, + "y": 41 + }, + { + "x": 28, + "y": 41 + }, + { + "x": 30, + "y": 41 + }, + { + "x": 32, + "y": 41 + }, + { + "x": 34, + "y": 41 + }, + { + "x": 36, + "y": 41 + }, + { + "x": 38, + "y": 41 + }, + { + "x": 40, + "y": 41 + }, + { + "x": 42, + "y": 41 + }, + { + "x": 44, + "y": 41 + }, + { + "x": 46, + "y": 41 + }, + { + "x": 48, + "y": 41 + }, + { + "x": 50, + "y": 41 + }, + { + "x": 52, + "y": 41 + }, + { + "x": 54, + "y": 41 + }, + { + "x": 56, + "y": 41 + }, + { + "x": 58, + "y": 41 + }, + { + "x": 60, + "y": 41 + }, + { + "x": 62, + "y": 41 + }, + { + "x": 64, + "y": 41 + }, + { + "x": 66, + "y": 41 + }, + { + "x": 68, + "y": 41 + }, + { + "x": 70, + "y": 41 + }, + { + "x": 72, + "y": 41 + }, + { + "x": 74, + "y": 41 + } + ], + "loading_areas": [ + { + "x": 3, + "y": 48 + }, + { + "x": 6, + "y": 48 + }, + { + "x": 9, + "y": 48 + }, + { + "x": 13, + "y": 48 + }, + { + "x": 19, + "y": 48 + }, + { + "x": 22, + "y": 48 + }, + { + "x": 25, + "y": 48 + }, + { + "x": 29, + "y": 48 + }, + { + "x": 35, + "y": 48 + }, + { + "x": 38, + "y": 48 + }, + { + "x": 41, + "y": 48 + }, + { + "x": 45, + "y": 48 + }, + { + "x": 51, + "y": 48 + }, + { + "x": 54, + "y": 48 + }, + { + "x": 57, + "y": 48 + }, + { + "x": 61, + "y": 48 + }, + { + "x": 67, + "y": 48 + }, + { + "x": 70, + "y": 48 + }, + { + "x": 73, + "y": 48 + }, + { + "x": 77, + "y": 48 + } + ], + "unloading_areas": [ + { + "x": 1, + "y": 48 + }, + { + "x": 4, + "y": 48 + }, + { + "x": 7, + "y": 48 + }, + { + "x": 11, + "y": 48 + }, + { + "x": 17, + "y": 48 + }, + { + "x": 20, + "y": 48 + }, + { + "x": 23, + "y": 48 + }, + { + "x": 27, + "y": 48 + }, + { + "x": 33, + "y": 48 + }, + { + "x": 36, + "y": 48 + }, + { + "x": 39, + "y": 48 + }, + { + "x": 43, + "y": 48 + }, + { + "x": 49, + "y": 48 + }, + { + "x": 52, + "y": 48 + }, + { + "x": 55, + "y": 48 + }, + { + "x": 59, + "y": 48 + }, + { + "x": 65, + "y": 48 + }, + { + "x": 68, + "y": 48 + }, + { + "x": 71, + "y": 48 + }, + { + "x": 75, + "y": 48 + } + ], + "obstacles": [ + { + "x": 0, + "y": 0 + }, + { + "x": 1, + "y": 0 + }, + { + "x": 2, + "y": 0 + }, + { + "x": 3, + "y": 0 + }, + { + "x": 4, + "y": 0 + }, + { + "x": 5, + "y": 0 + }, + { + "x": 6, + "y": 0 + }, + { + "x": 7, + "y": 0 + }, + { + "x": 8, + "y": 0 + }, + { + "x": 9, + "y": 0 + }, + { + "x": 10, + "y": 0 + }, + { + "x": 11, + "y": 0 + }, + { + "x": 12, + "y": 0 + }, + { + "x": 13, + "y": 0 + }, + { + "x": 14, + "y": 0 + }, + { + "x": 15, + "y": 0 + }, + { + "x": 16, + "y": 0 + }, + { + "x": 62, + "y": 0 + }, + { + "x": 63, + "y": 0 + }, + { + "x": 64, + "y": 0 + }, + { + "x": 65, + "y": 0 + }, + { + "x": 66, + "y": 0 + }, + { + "x": 67, + "y": 0 + }, + { + "x": 68, + "y": 0 + }, + { + "x": 69, + "y": 0 + }, + { + "x": 70, + "y": 0 + }, + { + "x": 71, + "y": 0 + }, + { + "x": 72, + "y": 0 + }, + { + "x": 73, + "y": 0 + }, + { + "x": 74, + "y": 0 + }, + { + "x": 75, + "y": 0 + }, + { + "x": 76, + "y": 0 + }, + { + "x": 77, + "y": 0 + }, + { + "x": 0, + "y": 1 + }, + { + "x": 1, + "y": 1 + }, + { + "x": 2, + "y": 1 + }, + { + "x": 3, + "y": 1 + }, + { + "x": 4, + "y": 1 + }, + { + "x": 5, + "y": 1 + }, + { + "x": 6, + "y": 1 + }, + { + "x": 7, + "y": 1 + }, + { + "x": 8, + "y": 1 + }, + { + "x": 9, + "y": 1 + }, + { + "x": 10, + "y": 1 + }, + { + "x": 11, + "y": 1 + }, + { + "x": 12, + "y": 1 + }, + { + "x": 13, + "y": 1 + }, + { + "x": 14, + "y": 1 + }, + { + "x": 15, + "y": 1 + }, + { + "x": 16, + "y": 1 + }, + { + "x": 62, + "y": 1 + }, + { + "x": 63, + "y": 1 + }, + { + "x": 64, + "y": 1 + }, + { + "x": 65, + "y": 1 + }, + { + "x": 66, + "y": 1 + }, + { + "x": 67, + "y": 1 + }, + { + "x": 68, + "y": 1 + }, + { + "x": 69, + "y": 1 + }, + { + "x": 70, + "y": 1 + }, + { + "x": 71, + "y": 1 + }, + { + "x": 72, + "y": 1 + }, + { + "x": 73, + "y": 1 + }, + { + "x": 74, + "y": 1 + }, + { + "x": 75, + "y": 1 + }, + { + "x": 76, + "y": 1 + }, + { + "x": 77, + "y": 1 + }, + { + "x": 0, + "y": 2 + }, + { + "x": 1, + "y": 2 + }, + { + "x": 2, + "y": 2 + }, + { + "x": 3, + "y": 2 + }, + { + "x": 4, + "y": 2 + }, + { + "x": 5, + "y": 2 + }, + { + "x": 6, + "y": 2 + }, + { + "x": 7, + "y": 2 + }, + { + "x": 8, + "y": 2 + }, + { + "x": 9, + "y": 2 + }, + { + "x": 10, + "y": 2 + }, + { + "x": 11, + "y": 2 + }, + { + "x": 12, + "y": 2 + }, + { + "x": 13, + "y": 2 + }, + { + "x": 14, + "y": 2 + }, + { + "x": 15, + "y": 2 + }, + { + "x": 16, + "y": 2 + }, + { + "x": 62, + "y": 2 + }, + { + "x": 63, + "y": 2 + }, + { + "x": 64, + "y": 2 + }, + { + "x": 65, + "y": 2 + }, + { + "x": 66, + "y": 2 + }, + { + "x": 67, + "y": 2 + }, + { + "x": 68, + "y": 2 + }, + { + "x": 69, + "y": 2 + }, + { + "x": 70, + "y": 2 + }, + { + "x": 71, + "y": 2 + }, + { + "x": 72, + "y": 2 + }, + { + "x": 73, + "y": 2 + }, + { + "x": 74, + "y": 2 + }, + { + "x": 75, + "y": 2 + }, + { + "x": 76, + "y": 2 + }, + { + "x": 77, + "y": 2 + }, + { + "x": 0, + "y": 3 + }, + { + "x": 1, + "y": 3 + }, + { + "x": 2, + "y": 3 + }, + { + "x": 3, + "y": 3 + }, + { + "x": 4, + "y": 3 + }, + { + "x": 5, + "y": 3 + }, + { + "x": 6, + "y": 3 + }, + { + "x": 7, + "y": 3 + }, + { + "x": 8, + "y": 3 + }, + { + "x": 9, + "y": 3 + }, + { + "x": 10, + "y": 3 + }, + { + "x": 11, + "y": 3 + }, + { + "x": 12, + "y": 3 + }, + { + "x": 13, + "y": 3 + }, + { + "x": 14, + "y": 3 + }, + { + "x": 15, + "y": 3 + }, + { + "x": 16, + "y": 3 + }, + { + "x": 62, + "y": 3 + }, + { + "x": 63, + "y": 3 + }, + { + "x": 64, + "y": 3 + }, + { + "x": 65, + "y": 3 + }, + { + "x": 66, + "y": 3 + }, + { + "x": 67, + "y": 3 + }, + { + "x": 68, + "y": 3 + }, + { + "x": 69, + "y": 3 + }, + { + "x": 70, + "y": 3 + }, + { + "x": 71, + "y": 3 + }, + { + "x": 72, + "y": 3 + }, + { + "x": 73, + "y": 3 + }, + { + "x": 74, + "y": 3 + }, + { + "x": 75, + "y": 3 + }, + { + "x": 76, + "y": 3 + }, + { + "x": 77, + "y": 3 + }, + { + "x": 0, + "y": 4 + }, + { + "x": 1, + "y": 4 + }, + { + "x": 2, + "y": 4 + }, + { + "x": 3, + "y": 4 + }, + { + "x": 4, + "y": 4 + }, + { + "x": 5, + "y": 4 + }, + { + "x": 6, + "y": 4 + }, + { + "x": 7, + "y": 4 + }, + { + "x": 8, + "y": 4 + }, + { + "x": 9, + "y": 4 + }, + { + "x": 10, + "y": 4 + }, + { + "x": 11, + "y": 4 + }, + { + "x": 12, + "y": 4 + }, + { + "x": 13, + "y": 4 + }, + { + "x": 14, + "y": 4 + }, + { + "x": 15, + "y": 4 + }, + { + "x": 16, + "y": 4 + }, + { + "x": 62, + "y": 4 + }, + { + "x": 63, + "y": 4 + }, + { + "x": 64, + "y": 4 + }, + { + "x": 65, + "y": 4 + }, + { + "x": 66, + "y": 4 + }, + { + "x": 67, + "y": 4 + }, + { + "x": 68, + "y": 4 + }, + { + "x": 69, + "y": 4 + }, + { + "x": 70, + "y": 4 + }, + { + "x": 71, + "y": 4 + }, + { + "x": 72, + "y": 4 + }, + { + "x": 73, + "y": 4 + }, + { + "x": 74, + "y": 4 + }, + { + "x": 75, + "y": 4 + }, + { + "x": 76, + "y": 4 + }, + { + "x": 77, + "y": 4 + }, + { + "x": 0, + "y": 5 + }, + { + "x": 1, + "y": 5 + }, + { + "x": 2, + "y": 5 + }, + { + "x": 3, + "y": 5 + }, + { + "x": 4, + "y": 5 + }, + { + "x": 5, + "y": 5 + }, + { + "x": 6, + "y": 5 + }, + { + "x": 7, + "y": 5 + }, + { + "x": 8, + "y": 5 + }, + { + "x": 9, + "y": 5 + }, + { + "x": 10, + "y": 5 + }, + { + "x": 11, + "y": 5 + }, + { + "x": 12, + "y": 5 + }, + { + "x": 13, + "y": 5 + }, + { + "x": 14, + "y": 5 + }, + { + "x": 15, + "y": 5 + }, + { + "x": 16, + "y": 5 + }, + { + "x": 62, + "y": 5 + }, + { + "x": 63, + "y": 5 + }, + { + "x": 64, + "y": 5 + }, + { + "x": 65, + "y": 5 + }, + { + "x": 66, + "y": 5 + }, + { + "x": 67, + "y": 5 + }, + { + "x": 68, + "y": 5 + }, + { + "x": 69, + "y": 5 + }, + { + "x": 70, + "y": 5 + }, + { + "x": 71, + "y": 5 + }, + { + "x": 72, + "y": 5 + }, + { + "x": 73, + "y": 5 + }, + { + "x": 74, + "y": 5 + }, + { + "x": 75, + "y": 5 + }, + { + "x": 76, + "y": 5 + }, + { + "x": 77, + "y": 5 + }, + { + "x": 0, + "y": 6 + }, + { + "x": 1, + "y": 6 + }, + { + "x": 2, + "y": 6 + }, + { + "x": 3, + "y": 6 + }, + { + "x": 4, + "y": 6 + }, + { + "x": 5, + "y": 6 + }, + { + "x": 6, + "y": 6 + }, + { + "x": 7, + "y": 6 + }, + { + "x": 8, + "y": 6 + }, + { + "x": 9, + "y": 6 + }, + { + "x": 10, + "y": 6 + }, + { + "x": 11, + "y": 6 + }, + { + "x": 12, + "y": 6 + }, + { + "x": 13, + "y": 6 + }, + { + "x": 14, + "y": 6 + }, + { + "x": 15, + "y": 6 + }, + { + "x": 16, + "y": 6 + }, + { + "x": 62, + "y": 6 + }, + { + "x": 63, + "y": 6 + }, + { + "x": 64, + "y": 6 + }, + { + "x": 65, + "y": 6 + }, + { + "x": 66, + "y": 6 + }, + { + "x": 67, + "y": 6 + }, + { + "x": 68, + "y": 6 + }, + { + "x": 69, + "y": 6 + }, + { + "x": 70, + "y": 6 + }, + { + "x": 71, + "y": 6 + }, + { + "x": 72, + "y": 6 + }, + { + "x": 73, + "y": 6 + }, + { + "x": 74, + "y": 6 + }, + { + "x": 75, + "y": 6 + }, + { + "x": 76, + "y": 6 + }, + { + "x": 77, + "y": 6 + }, + { + "x": 0, + "y": 7 + }, + { + "x": 1, + "y": 7 + }, + { + "x": 2, + "y": 7 + }, + { + "x": 3, + "y": 7 + }, + { + "x": 4, + "y": 7 + }, + { + "x": 5, + "y": 7 + }, + { + "x": 6, + "y": 7 + }, + { + "x": 7, + "y": 7 + }, + { + "x": 8, + "y": 7 + }, + { + "x": 9, + "y": 7 + }, + { + "x": 10, + "y": 7 + }, + { + "x": 11, + "y": 7 + }, + { + "x": 12, + "y": 7 + }, + { + "x": 13, + "y": 7 + }, + { + "x": 14, + "y": 7 + }, + { + "x": 15, + "y": 7 + }, + { + "x": 16, + "y": 7 + }, + { + "x": 62, + "y": 7 + }, + { + "x": 63, + "y": 7 + }, + { + "x": 64, + "y": 7 + }, + { + "x": 65, + "y": 7 + }, + { + "x": 66, + "y": 7 + }, + { + "x": 67, + "y": 7 + }, + { + "x": 68, + "y": 7 + }, + { + "x": 69, + "y": 7 + }, + { + "x": 70, + "y": 7 + }, + { + "x": 71, + "y": 7 + }, + { + "x": 72, + "y": 7 + }, + { + "x": 73, + "y": 7 + }, + { + "x": 74, + "y": 7 + }, + { + "x": 75, + "y": 7 + }, + { + "x": 76, + "y": 7 + }, + { + "x": 77, + "y": 7 + }, + { + "x": 0, + "y": 8 + }, + { + "x": 1, + "y": 8 + }, + { + "x": 2, + "y": 8 + }, + { + "x": 3, + "y": 8 + }, + { + "x": 4, + "y": 8 + }, + { + "x": 5, + "y": 8 + }, + { + "x": 6, + "y": 8 + }, + { + "x": 7, + "y": 8 + }, + { + "x": 8, + "y": 8 + }, + { + "x": 9, + "y": 8 + }, + { + "x": 10, + "y": 8 + }, + { + "x": 11, + "y": 8 + }, + { + "x": 12, + "y": 8 + }, + { + "x": 13, + "y": 8 + }, + { + "x": 14, + "y": 8 + }, + { + "x": 15, + "y": 8 + }, + { + "x": 16, + "y": 8 + }, + { + "x": 62, + "y": 8 + }, + { + "x": 63, + "y": 8 + }, + { + "x": 64, + "y": 8 + }, + { + "x": 65, + "y": 8 + }, + { + "x": 66, + "y": 8 + }, + { + "x": 67, + "y": 8 + }, + { + "x": 68, + "y": 8 + }, + { + "x": 69, + "y": 8 + }, + { + "x": 70, + "y": 8 + }, + { + "x": 71, + "y": 8 + }, + { + "x": 72, + "y": 8 + }, + { + "x": 73, + "y": 8 + }, + { + "x": 74, + "y": 8 + }, + { + "x": 75, + "y": 8 + }, + { + "x": 76, + "y": 8 + }, + { + "x": 77, + "y": 8 + }, + { + "x": 0, + "y": 9 + }, + { + "x": 1, + "y": 9 + }, + { + "x": 2, + "y": 9 + }, + { + "x": 3, + "y": 9 + }, + { + "x": 4, + "y": 9 + }, + { + "x": 5, + "y": 9 + }, + { + "x": 6, + "y": 9 + }, + { + "x": 7, + "y": 9 + }, + { + "x": 8, + "y": 9 + }, + { + "x": 9, + "y": 9 + }, + { + "x": 10, + "y": 9 + }, + { + "x": 11, + "y": 9 + }, + { + "x": 12, + "y": 9 + }, + { + "x": 13, + "y": 9 + }, + { + "x": 14, + "y": 9 + }, + { + "x": 15, + "y": 9 + }, + { + "x": 16, + "y": 9 + }, + { + "x": 62, + "y": 9 + }, + { + "x": 63, + "y": 9 + }, + { + "x": 64, + "y": 9 + }, + { + "x": 65, + "y": 9 + }, + { + "x": 66, + "y": 9 + }, + { + "x": 67, + "y": 9 + }, + { + "x": 68, + "y": 9 + }, + { + "x": 69, + "y": 9 + }, + { + "x": 70, + "y": 9 + }, + { + "x": 71, + "y": 9 + }, + { + "x": 72, + "y": 9 + }, + { + "x": 73, + "y": 9 + }, + { + "x": 74, + "y": 9 + }, + { + "x": 75, + "y": 9 + }, + { + "x": 76, + "y": 9 + }, + { + "x": 77, + "y": 9 + }, + { + "x": 0, + "y": 10 + }, + { + "x": 1, + "y": 10 + }, + { + "x": 2, + "y": 10 + }, + { + "x": 3, + "y": 10 + }, + { + "x": 4, + "y": 10 + }, + { + "x": 5, + "y": 10 + }, + { + "x": 6, + "y": 10 + }, + { + "x": 7, + "y": 10 + }, + { + "x": 8, + "y": 10 + }, + { + "x": 9, + "y": 10 + }, + { + "x": 10, + "y": 10 + }, + { + "x": 11, + "y": 10 + }, + { + "x": 12, + "y": 10 + }, + { + "x": 13, + "y": 10 + }, + { + "x": 14, + "y": 10 + }, + { + "x": 15, + "y": 10 + }, + { + "x": 16, + "y": 10 + }, + { + "x": 62, + "y": 10 + }, + { + "x": 63, + "y": 10 + }, + { + "x": 64, + "y": 10 + }, + { + "x": 65, + "y": 10 + }, + { + "x": 66, + "y": 10 + }, + { + "x": 67, + "y": 10 + }, + { + "x": 68, + "y": 10 + }, + { + "x": 69, + "y": 10 + }, + { + "x": 70, + "y": 10 + }, + { + "x": 71, + "y": 10 + }, + { + "x": 72, + "y": 10 + }, + { + "x": 73, + "y": 10 + }, + { + "x": 74, + "y": 10 + }, + { + "x": 75, + "y": 10 + }, + { + "x": 76, + "y": 10 + }, + { + "x": 77, + "y": 10 + }, + { + "x": 0, + "y": 11 + }, + { + "x": 1, + "y": 11 + }, + { + "x": 2, + "y": 11 + }, + { + "x": 3, + "y": 11 + }, + { + "x": 4, + "y": 11 + }, + { + "x": 5, + "y": 11 + }, + { + "x": 6, + "y": 11 + }, + { + "x": 7, + "y": 11 + }, + { + "x": 8, + "y": 11 + }, + { + "x": 9, + "y": 11 + }, + { + "x": 10, + "y": 11 + }, + { + "x": 11, + "y": 11 + }, + { + "x": 12, + "y": 11 + }, + { + "x": 13, + "y": 11 + }, + { + "x": 14, + "y": 11 + }, + { + "x": 15, + "y": 11 + }, + { + "x": 16, + "y": 11 + }, + { + "x": 62, + "y": 11 + }, + { + "x": 63, + "y": 11 + }, + { + "x": 64, + "y": 11 + }, + { + "x": 65, + "y": 11 + }, + { + "x": 66, + "y": 11 + }, + { + "x": 67, + "y": 11 + }, + { + "x": 68, + "y": 11 + }, + { + "x": 69, + "y": 11 + }, + { + "x": 70, + "y": 11 + }, + { + "x": 71, + "y": 11 + }, + { + "x": 72, + "y": 11 + }, + { + "x": 73, + "y": 11 + }, + { + "x": 74, + "y": 11 + }, + { + "x": 75, + "y": 11 + }, + { + "x": 76, + "y": 11 + }, + { + "x": 77, + "y": 11 + }, + { + "x": 76, + "y": 12 + }, + { + "x": 77, + "y": 12 + }, + { + "x": 76, + "y": 13 + }, + { + "x": 77, + "y": 13 + }, + { + "x": 76, + "y": 14 + }, + { + "x": 77, + "y": 14 + }, + { + "x": 76, + "y": 15 + }, + { + "x": 77, + "y": 15 + }, + { + "x": 76, + "y": 16 + }, + { + "x": 77, + "y": 16 + }, + { + "x": 76, + "y": 17 + }, + { + "x": 77, + "y": 17 + }, + { + "x": 76, + "y": 18 + }, + { + "x": 77, + "y": 18 + }, + { + "x": 76, + "y": 19 + }, + { + "x": 77, + "y": 19 + }, + { + "x": 76, + "y": 20 + }, + { + "x": 77, + "y": 20 + }, + { + "x": 76, + "y": 21 + }, + { + "x": 77, + "y": 21 + }, + { + "x": 76, + "y": 22 + }, + { + "x": 77, + "y": 22 + }, + { + "x": 7, + "y": 23 + }, + { + "x": 15, + "y": 23 + }, + { + "x": 23, + "y": 23 + }, + { + "x": 31, + "y": 23 + }, + { + "x": 39, + "y": 23 + }, + { + "x": 47, + "y": 23 + }, + { + "x": 55, + "y": 23 + }, + { + "x": 63, + "y": 23 + }, + { + "x": 71, + "y": 23 + }, + { + "x": 76, + "y": 23 + }, + { + "x": 77, + "y": 23 + }, + { + "x": 7, + "y": 24 + }, + { + "x": 15, + "y": 24 + }, + { + "x": 23, + "y": 24 + }, + { + "x": 31, + "y": 24 + }, + { + "x": 39, + "y": 24 + }, + { + "x": 47, + "y": 24 + }, + { + "x": 55, + "y": 24 + }, + { + "x": 63, + "y": 24 + }, + { + "x": 71, + "y": 24 + }, + { + "x": 76, + "y": 24 + }, + { + "x": 77, + "y": 24 + }, + { + "x": 7, + "y": 25 + }, + { + "x": 15, + "y": 25 + }, + { + "x": 23, + "y": 25 + }, + { + "x": 31, + "y": 25 + }, + { + "x": 39, + "y": 25 + }, + { + "x": 47, + "y": 25 + }, + { + "x": 55, + "y": 25 + }, + { + "x": 63, + "y": 25 + }, + { + "x": 71, + "y": 25 + }, + { + "x": 76, + "y": 25 + }, + { + "x": 77, + "y": 25 + }, + { + "x": 7, + "y": 26 + }, + { + "x": 15, + "y": 26 + }, + { + "x": 23, + "y": 26 + }, + { + "x": 31, + "y": 26 + }, + { + "x": 39, + "y": 26 + }, + { + "x": 47, + "y": 26 + }, + { + "x": 55, + "y": 26 + }, + { + "x": 63, + "y": 26 + }, + { + "x": 71, + "y": 26 + }, + { + "x": 76, + "y": 26 + }, + { + "x": 77, + "y": 26 + }, + { + "x": 7, + "y": 27 + }, + { + "x": 15, + "y": 27 + }, + { + "x": 23, + "y": 27 + }, + { + "x": 31, + "y": 27 + }, + { + "x": 39, + "y": 27 + }, + { + "x": 47, + "y": 27 + }, + { + "x": 55, + "y": 27 + }, + { + "x": 63, + "y": 27 + }, + { + "x": 71, + "y": 27 + }, + { + "x": 76, + "y": 27 + }, + { + "x": 77, + "y": 27 + }, + { + "x": 76, + "y": 28 + }, + { + "x": 77, + "y": 28 + }, + { + "x": 76, + "y": 29 + }, + { + "x": 77, + "y": 29 + }, + { + "x": 76, + "y": 30 + }, + { + "x": 77, + "y": 30 + }, + { + "x": 76, + "y": 31 + }, + { + "x": 77, + "y": 31 + }, + { + "x": 0, + "y": 49 + }, + { + "x": 1, + "y": 49 + }, + { + "x": 2, + "y": 49 + }, + { + "x": 3, + "y": 49 + }, + { + "x": 4, + "y": 49 + }, + { + "x": 5, + "y": 49 + }, + { + "x": 6, + "y": 49 + }, + { + "x": 7, + "y": 49 + }, + { + "x": 8, + "y": 49 + }, + { + "x": 9, + "y": 49 + }, + { + "x": 10, + "y": 49 + }, + { + "x": 11, + "y": 49 + }, + { + "x": 12, + "y": 49 + }, + { + "x": 13, + "y": 49 + }, + { + "x": 14, + "y": 49 + }, + { + "x": 15, + "y": 49 + }, + { + "x": 16, + "y": 49 + }, + { + "x": 17, + "y": 49 + }, + { + "x": 18, + "y": 49 + }, + { + "x": 19, + "y": 49 + }, + { + "x": 20, + "y": 49 + }, + { + "x": 21, + "y": 49 + }, + { + "x": 22, + "y": 49 + }, + { + "x": 23, + "y": 49 + }, + { + "x": 24, + "y": 49 + }, + { + "x": 25, + "y": 49 + }, + { + "x": 26, + "y": 49 + }, + { + "x": 27, + "y": 49 + }, + { + "x": 28, + "y": 49 + }, + { + "x": 29, + "y": 49 + }, + { + "x": 30, + "y": 49 + }, + { + "x": 31, + "y": 49 + }, + { + "x": 32, + "y": 49 + }, + { + "x": 33, + "y": 49 + }, + { + "x": 34, + "y": 49 + }, + { + "x": 35, + "y": 49 + }, + { + "x": 36, + "y": 49 + }, + { + "x": 37, + "y": 49 + }, + { + "x": 38, + "y": 49 + }, + { + "x": 39, + "y": 49 + }, + { + "x": 40, + "y": 49 + }, + { + "x": 41, + "y": 49 + }, + { + "x": 42, + "y": 49 + }, + { + "x": 43, + "y": 49 + }, + { + "x": 44, + "y": 49 + }, + { + "x": 45, + "y": 49 + }, + { + "x": 46, + "y": 49 + }, + { + "x": 47, + "y": 49 + }, + { + "x": 48, + "y": 49 + }, + { + "x": 49, + "y": 49 + }, + { + "x": 50, + "y": 49 + }, + { + "x": 51, + "y": 49 + }, + { + "x": 52, + "y": 49 + }, + { + "x": 53, + "y": 49 + }, + { + "x": 54, + "y": 49 + }, + { + "x": 55, + "y": 49 + }, + { + "x": 56, + "y": 49 + }, + { + "x": 57, + "y": 49 + }, + { + "x": 58, + "y": 49 + }, + { + "x": 59, + "y": 49 + }, + { + "x": 60, + "y": 49 + }, + { + "x": 61, + "y": 49 + }, + { + "x": 62, + "y": 49 + }, + { + "x": 63, + "y": 49 + }, + { + "x": 64, + "y": 49 + }, + { + "x": 65, + "y": 49 + }, + { + "x": 66, + "y": 49 + }, + { + "x": 67, + "y": 49 + }, + { + "x": 68, + "y": 49 + }, + { + "x": 69, + "y": 49 + }, + { + "x": 70, + "y": 49 + }, + { + "x": 71, + "y": 49 + }, + { + "x": 72, + "y": 49 + }, + { + "x": 73, + "y": 49 + }, + { + "x": 74, + "y": 49 + }, + { + "x": 75, + "y": 49 + }, + { + "x": 76, + "y": 49 + }, + { + "x": 77, + "y": 49 + } + ] +} \ No newline at end of file diff --git a/path_mapping.json b/path_mapping.json new file mode 100644 index 0000000..bbb94bc --- /dev/null +++ b/path_mapping.json @@ -0,0 +1,11880 @@ +{ + "path_id_to_coordinates": { + "338": [ + { + "x": 17, + "y": 0 + } + ], + "346": [ + { + "x": 18, + "y": 0 + } + ], + "393": [ + { + "x": 19, + "y": 0 + } + ], + "401": [ + { + "x": 20, + "y": 0 + } + ], + "448": [ + { + "x": 21, + "y": 0 + } + ], + "456": [ + { + "x": 22, + "y": 0 + } + ], + "482": [ + { + "x": 23, + "y": 0 + } + ], + "490": [ + { + "x": 24, + "y": 0 + } + ], + "537": [ + { + "x": 25, + "y": 0 + } + ], + "544": [ + { + "x": 26, + "y": 0 + } + ], + "592": [ + { + "x": 27, + "y": 0 + } + ], + "599": [ + { + "x": 28, + "y": 0 + } + ], + "647": [ + { + "x": 29, + "y": 0 + } + ], + "654": [ + { + "x": 30, + "y": 0 + } + ], + "679": [ + { + "x": 31, + "y": 0 + } + ], + "686": [ + { + "x": 32, + "y": 0 + } + ], + "733": [ + { + "x": 33, + "y": 0 + } + ], + "741": [ + { + "x": 34, + "y": 0 + } + ], + "788": [ + { + "x": 35, + "y": 0 + } + ], + "796": [ + { + "x": 36, + "y": 0 + } + ], + "843": [ + { + "x": 37, + "y": 0 + } + ], + "851": [ + { + "x": 38, + "y": 0 + } + ], + "877": [ + { + "x": 39, + "y": 0 + } + ], + "885": [ + { + "x": 40, + "y": 0 + } + ], + "932": [ + { + "x": 41, + "y": 0 + } + ], + "939": [ + { + "x": 42, + "y": 0 + } + ], + "987": [ + { + "x": 43, + "y": 0 + } + ], + "994": [ + { + "x": 44, + "y": 0 + } + ], + "1042": [ + { + "x": 45, + "y": 0 + } + ], + "1049": [ + { + "x": 46, + "y": 0 + } + ], + "1074": [ + { + "x": 47, + "y": 0 + } + ], + "1081": [ + { + "x": 48, + "y": 0 + } + ], + "1128": [ + { + "x": 49, + "y": 0 + } + ], + "1136": [ + { + "x": 50, + "y": 0 + } + ], + "1183": [ + { + "x": 51, + "y": 0 + } + ], + "1191": [ + { + "x": 52, + "y": 0 + } + ], + "1238": [ + { + "x": 53, + "y": 0 + } + ], + "1246": [ + { + "x": 54, + "y": 0 + } + ], + "1272": [ + { + "x": 55, + "y": 0 + } + ], + "1280": [ + { + "x": 56, + "y": 0 + } + ], + "1327": [ + { + "x": 57, + "y": 0 + } + ], + "1334": [ + { + "x": 58, + "y": 0 + } + ], + "1382": [ + { + "x": 59, + "y": 0 + } + ], + "1389": [ + { + "x": 60, + "y": 0 + } + ], + "1437": [ + { + "x": 61, + "y": 0 + } + ], + "337": [ + { + "x": 17, + "y": 1 + } + ], + "392": [ + { + "x": 19, + "y": 1 + } + ], + "447": [ + { + "x": 21, + "y": 1 + } + ], + "536": [ + { + "x": 25, + "y": 1 + } + ], + "591": [ + { + "x": 27, + "y": 1 + } + ], + "646": [ + { + "x": 29, + "y": 1 + } + ], + "732": [ + { + "x": 33, + "y": 1 + } + ], + "787": [ + { + "x": 35, + "y": 1 + } + ], + "842": [ + { + "x": 37, + "y": 1 + } + ], + "931": [ + { + "x": 41, + "y": 1 + } + ], + "986": [ + { + "x": 43, + "y": 1 + } + ], + "1041": [ + { + "x": 45, + "y": 1 + } + ], + "1127": [ + { + "x": 49, + "y": 1 + } + ], + "1182": [ + { + "x": 51, + "y": 1 + } + ], + "1237": [ + { + "x": 53, + "y": 1 + } + ], + "1326": [ + { + "x": 57, + "y": 1 + } + ], + "1381": [ + { + "x": 59, + "y": 1 + } + ], + "1436": [ + { + "x": 61, + "y": 1 + } + ], + "336": [ + { + "x": 17, + "y": 2 + } + ], + "391": [ + { + "x": 19, + "y": 2 + } + ], + "446": [ + { + "x": 21, + "y": 2 + } + ], + "481": [ + { + "x": 23, + "y": 2 + } + ], + "535": [ + { + "x": 25, + "y": 2 + } + ], + "590": [ + { + "x": 27, + "y": 2 + } + ], + "645": [ + { + "x": 29, + "y": 2 + } + ], + "678": [ + { + "x": 31, + "y": 2 + } + ], + "731": [ + { + "x": 33, + "y": 2 + } + ], + "786": [ + { + "x": 35, + "y": 2 + } + ], + "841": [ + { + "x": 37, + "y": 2 + } + ], + "876": [ + { + "x": 39, + "y": 2 + } + ], + "930": [ + { + "x": 41, + "y": 2 + } + ], + "985": [ + { + "x": 43, + "y": 2 + } + ], + "1040": [ + { + "x": 45, + "y": 2 + } + ], + "1073": [ + { + "x": 47, + "y": 2 + } + ], + "1126": [ + { + "x": 49, + "y": 2 + } + ], + "1181": [ + { + "x": 51, + "y": 2 + } + ], + "1236": [ + { + "x": 53, + "y": 2 + } + ], + "1271": [ + { + "x": 55, + "y": 2 + } + ], + "1325": [ + { + "x": 57, + "y": 2 + } + ], + "1380": [ + { + "x": 59, + "y": 2 + } + ], + "1435": [ + { + "x": 61, + "y": 2 + } + ], + "335": [ + { + "x": 17, + "y": 3 + } + ], + "390": [ + { + "x": 19, + "y": 3 + } + ], + "445": [ + { + "x": 21, + "y": 3 + } + ], + "480": [ + { + "x": 23, + "y": 3 + } + ], + "534": [ + { + "x": 25, + "y": 3 + } + ], + "589": [ + { + "x": 27, + "y": 3 + } + ], + "644": [ + { + "x": 29, + "y": 3 + } + ], + "677": [ + { + "x": 31, + "y": 3 + } + ], + "730": [ + { + "x": 33, + "y": 3 + } + ], + "785": [ + { + "x": 35, + "y": 3 + } + ], + "840": [ + { + "x": 37, + "y": 3 + } + ], + "875": [ + { + "x": 39, + "y": 3 + } + ], + "929": [ + { + "x": 41, + "y": 3 + } + ], + "984": [ + { + "x": 43, + "y": 3 + } + ], + "1039": [ + { + "x": 45, + "y": 3 + } + ], + "1072": [ + { + "x": 47, + "y": 3 + } + ], + "1125": [ + { + "x": 49, + "y": 3 + } + ], + "1180": [ + { + "x": 51, + "y": 3 + } + ], + "1235": [ + { + "x": 53, + "y": 3 + } + ], + "1270": [ + { + "x": 55, + "y": 3 + } + ], + "1324": [ + { + "x": 57, + "y": 3 + } + ], + "1379": [ + { + "x": 59, + "y": 3 + } + ], + "1434": [ + { + "x": 61, + "y": 3 + } + ], + "334": [ + { + "x": 17, + "y": 4 + } + ], + "389": [ + { + "x": 19, + "y": 4 + } + ], + "444": [ + { + "x": 21, + "y": 4 + } + ], + "479": [ + { + "x": 23, + "y": 4 + } + ], + "533": [ + { + "x": 25, + "y": 4 + } + ], + "588": [ + { + "x": 27, + "y": 4 + } + ], + "643": [ + { + "x": 29, + "y": 4 + } + ], + "676": [ + { + "x": 31, + "y": 4 + } + ], + "729": [ + { + "x": 33, + "y": 4 + } + ], + "784": [ + { + "x": 35, + "y": 4 + } + ], + "839": [ + { + "x": 37, + "y": 4 + } + ], + "874": [ + { + "x": 39, + "y": 4 + } + ], + "928": [ + { + "x": 41, + "y": 4 + } + ], + "983": [ + { + "x": 43, + "y": 4 + } + ], + "1038": [ + { + "x": 45, + "y": 4 + } + ], + "1071": [ + { + "x": 47, + "y": 4 + } + ], + "1124": [ + { + "x": 49, + "y": 4 + } + ], + "1179": [ + { + "x": 51, + "y": 4 + } + ], + "1234": [ + { + "x": 53, + "y": 4 + } + ], + "1269": [ + { + "x": 55, + "y": 4 + } + ], + "1323": [ + { + "x": 57, + "y": 4 + } + ], + "1378": [ + { + "x": 59, + "y": 4 + } + ], + "1433": [ + { + "x": 61, + "y": 4 + } + ], + "333": [ + { + "x": 17, + "y": 5 + } + ], + "388": [ + { + "x": 19, + "y": 5 + } + ], + "443": [ + { + "x": 21, + "y": 5 + } + ], + "532": [ + { + "x": 25, + "y": 5 + } + ], + "587": [ + { + "x": 27, + "y": 5 + } + ], + "642": [ + { + "x": 29, + "y": 5 + } + ], + "728": [ + { + "x": 33, + "y": 5 + } + ], + "783": [ + { + "x": 35, + "y": 5 + } + ], + "838": [ + { + "x": 37, + "y": 5 + } + ], + "927": [ + { + "x": 41, + "y": 5 + } + ], + "982": [ + { + "x": 43, + "y": 5 + } + ], + "1037": [ + { + "x": 45, + "y": 5 + } + ], + "1123": [ + { + "x": 49, + "y": 5 + } + ], + "1178": [ + { + "x": 51, + "y": 5 + } + ], + "1233": [ + { + "x": 53, + "y": 5 + } + ], + "1322": [ + { + "x": 57, + "y": 5 + } + ], + "1377": [ + { + "x": 59, + "y": 5 + } + ], + "1432": [ + { + "x": 61, + "y": 5 + } + ], + "332": [ + { + "x": 17, + "y": 6 + } + ], + "345": [ + { + "x": 18, + "y": 6 + } + ], + "387": [ + { + "x": 19, + "y": 6 + } + ], + "400": [ + { + "x": 20, + "y": 6 + } + ], + "442": [ + { + "x": 21, + "y": 6 + } + ], + "455": [ + { + "x": 22, + "y": 6 + } + ], + "478": [ + { + "x": 23, + "y": 6 + } + ], + "489": [ + { + "x": 24, + "y": 6 + } + ], + "531": [ + { + "x": 25, + "y": 6 + } + ], + "543": [ + { + "x": 26, + "y": 6 + } + ], + "586": [ + { + "x": 27, + "y": 6 + } + ], + "598": [ + { + "x": 28, + "y": 6 + } + ], + "641": [ + { + "x": 29, + "y": 6 + } + ], + "653": [ + { + "x": 30, + "y": 6 + } + ], + "675": [ + { + "x": 31, + "y": 6 + } + ], + "685": [ + { + "x": 32, + "y": 6 + } + ], + "727": [ + { + "x": 33, + "y": 6 + } + ], + "740": [ + { + "x": 34, + "y": 6 + } + ], + "782": [ + { + "x": 35, + "y": 6 + } + ], + "795": [ + { + "x": 36, + "y": 6 + } + ], + "837": [ + { + "x": 37, + "y": 6 + } + ], + "850": [ + { + "x": 38, + "y": 6 + } + ], + "873": [ + { + "x": 39, + "y": 6 + } + ], + "884": [ + { + "x": 40, + "y": 6 + } + ], + "926": [ + { + "x": 41, + "y": 6 + } + ], + "938": [ + { + "x": 42, + "y": 6 + } + ], + "981": [ + { + "x": 43, + "y": 6 + } + ], + "993": [ + { + "x": 44, + "y": 6 + } + ], + "1036": [ + { + "x": 45, + "y": 6 + } + ], + "1048": [ + { + "x": 46, + "y": 6 + } + ], + "1070": [ + { + "x": 47, + "y": 6 + } + ], + "1080": [ + { + "x": 48, + "y": 6 + } + ], + "1122": [ + { + "x": 49, + "y": 6 + } + ], + "1135": [ + { + "x": 50, + "y": 6 + } + ], + "1177": [ + { + "x": 51, + "y": 6 + } + ], + "1190": [ + { + "x": 52, + "y": 6 + } + ], + "1232": [ + { + "x": 53, + "y": 6 + } + ], + "1245": [ + { + "x": 54, + "y": 6 + } + ], + "1268": [ + { + "x": 55, + "y": 6 + } + ], + "1279": [ + { + "x": 56, + "y": 6 + } + ], + "1321": [ + { + "x": 57, + "y": 6 + } + ], + "1333": [ + { + "x": 58, + "y": 6 + } + ], + "1376": [ + { + "x": 59, + "y": 6 + } + ], + "1388": [ + { + "x": 60, + "y": 6 + } + ], + "1431": [ + { + "x": 61, + "y": 6 + } + ], + "331": [ + { + "x": 17, + "y": 7 + } + ], + "386": [ + { + "x": 19, + "y": 7 + } + ], + "441": [ + { + "x": 21, + "y": 7 + } + ], + "530": [ + { + "x": 25, + "y": 7 + } + ], + "585": [ + { + "x": 27, + "y": 7 + } + ], + "640": [ + { + "x": 29, + "y": 7 + } + ], + "726": [ + { + "x": 33, + "y": 7 + } + ], + "781": [ + { + "x": 35, + "y": 7 + } + ], + "836": [ + { + "x": 37, + "y": 7 + } + ], + "925": [ + { + "x": 41, + "y": 7 + } + ], + "980": [ + { + "x": 43, + "y": 7 + } + ], + "1035": [ + { + "x": 45, + "y": 7 + } + ], + "1121": [ + { + "x": 49, + "y": 7 + } + ], + "1176": [ + { + "x": 51, + "y": 7 + } + ], + "1231": [ + { + "x": 53, + "y": 7 + } + ], + "1320": [ + { + "x": 57, + "y": 7 + } + ], + "1375": [ + { + "x": 59, + "y": 7 + } + ], + "1430": [ + { + "x": 61, + "y": 7 + } + ], + "330": [ + { + "x": 17, + "y": 8 + } + ], + "385": [ + { + "x": 19, + "y": 8 + } + ], + "440": [ + { + "x": 21, + "y": 8 + } + ], + "477": [ + { + "x": 23, + "y": 8 + } + ], + "529": [ + { + "x": 25, + "y": 8 + } + ], + "584": [ + { + "x": 27, + "y": 8 + } + ], + "639": [ + { + "x": 29, + "y": 8 + } + ], + "674": [ + { + "x": 31, + "y": 8 + } + ], + "725": [ + { + "x": 33, + "y": 8 + } + ], + "780": [ + { + "x": 35, + "y": 8 + } + ], + "835": [ + { + "x": 37, + "y": 8 + } + ], + "872": [ + { + "x": 39, + "y": 8 + } + ], + "924": [ + { + "x": 41, + "y": 8 + } + ], + "979": [ + { + "x": 43, + "y": 8 + } + ], + "1034": [ + { + "x": 45, + "y": 8 + } + ], + "1069": [ + { + "x": 47, + "y": 8 + } + ], + "1120": [ + { + "x": 49, + "y": 8 + } + ], + "1175": [ + { + "x": 51, + "y": 8 + } + ], + "1230": [ + { + "x": 53, + "y": 8 + } + ], + "1267": [ + { + "x": 55, + "y": 8 + } + ], + "1319": [ + { + "x": 57, + "y": 8 + } + ], + "1374": [ + { + "x": 59, + "y": 8 + } + ], + "1429": [ + { + "x": 61, + "y": 8 + } + ], + "329": [ + { + "x": 17, + "y": 9 + } + ], + "384": [ + { + "x": 19, + "y": 9 + } + ], + "439": [ + { + "x": 21, + "y": 9 + } + ], + "476": [ + { + "x": 23, + "y": 9 + } + ], + "528": [ + { + "x": 25, + "y": 9 + } + ], + "583": [ + { + "x": 27, + "y": 9 + } + ], + "638": [ + { + "x": 29, + "y": 9 + } + ], + "673": [ + { + "x": 31, + "y": 9 + } + ], + "724": [ + { + "x": 33, + "y": 9 + } + ], + "779": [ + { + "x": 35, + "y": 9 + } + ], + "834": [ + { + "x": 37, + "y": 9 + } + ], + "871": [ + { + "x": 39, + "y": 9 + } + ], + "923": [ + { + "x": 41, + "y": 9 + } + ], + "978": [ + { + "x": 43, + "y": 9 + } + ], + "1033": [ + { + "x": 45, + "y": 9 + } + ], + "1068": [ + { + "x": 47, + "y": 9 + } + ], + "1119": [ + { + "x": 49, + "y": 9 + } + ], + "1174": [ + { + "x": 51, + "y": 9 + } + ], + "1229": [ + { + "x": 53, + "y": 9 + } + ], + "1266": [ + { + "x": 55, + "y": 9 + } + ], + "1318": [ + { + "x": 57, + "y": 9 + } + ], + "1373": [ + { + "x": 59, + "y": 9 + } + ], + "1428": [ + { + "x": 61, + "y": 9 + } + ], + "328": [ + { + "x": 17, + "y": 10 + } + ], + "383": [ + { + "x": 19, + "y": 10 + } + ], + "438": [ + { + "x": 21, + "y": 10 + } + ], + "475": [ + { + "x": 23, + "y": 10 + } + ], + "527": [ + { + "x": 25, + "y": 10 + } + ], + "582": [ + { + "x": 27, + "y": 10 + } + ], + "637": [ + { + "x": 29, + "y": 10 + } + ], + "672": [ + { + "x": 31, + "y": 10 + } + ], + "723": [ + { + "x": 33, + "y": 10 + } + ], + "778": [ + { + "x": 35, + "y": 10 + } + ], + "833": [ + { + "x": 37, + "y": 10 + } + ], + "870": [ + { + "x": 39, + "y": 10 + } + ], + "922": [ + { + "x": 41, + "y": 10 + } + ], + "977": [ + { + "x": 43, + "y": 10 + } + ], + "1032": [ + { + "x": 45, + "y": 10 + } + ], + "1067": [ + { + "x": 47, + "y": 10 + } + ], + "1118": [ + { + "x": 49, + "y": 10 + } + ], + "1173": [ + { + "x": 51, + "y": 10 + } + ], + "1228": [ + { + "x": 53, + "y": 10 + } + ], + "1265": [ + { + "x": 55, + "y": 10 + } + ], + "1317": [ + { + "x": 57, + "y": 10 + } + ], + "1372": [ + { + "x": 59, + "y": 10 + } + ], + "1427": [ + { + "x": 61, + "y": 10 + } + ], + "327": [ + { + "x": 17, + "y": 11 + } + ], + "382": [ + { + "x": 19, + "y": 11 + } + ], + "437": [ + { + "x": 21, + "y": 11 + } + ], + "526": [ + { + "x": 25, + "y": 11 + } + ], + "581": [ + { + "x": 27, + "y": 11 + } + ], + "636": [ + { + "x": 29, + "y": 11 + } + ], + "722": [ + { + "x": 33, + "y": 11 + } + ], + "777": [ + { + "x": 35, + "y": 11 + } + ], + "832": [ + { + "x": 37, + "y": 11 + } + ], + "921": [ + { + "x": 41, + "y": 11 + } + ], + "976": [ + { + "x": 43, + "y": 11 + } + ], + "1031": [ + { + "x": 45, + "y": 11 + } + ], + "1117": [ + { + "x": 49, + "y": 11 + } + ], + "1172": [ + { + "x": 51, + "y": 11 + } + ], + "1227": [ + { + "x": 53, + "y": 11 + } + ], + "1316": [ + { + "x": 57, + "y": 11 + } + ], + "1371": [ + { + "x": 59, + "y": 11 + } + ], + "1426": [ + { + "x": 61, + "y": 11 + } + ], + "35": [ + { + "x": 1, + "y": 12 + } + ], + "41": [ + { + "x": 2, + "y": 12 + } + ], + "76": [ + { + "x": 3, + "y": 12 + } + ], + "82": [ + { + "x": 4, + "y": 12 + } + ], + "117": [ + { + "x": 5, + "y": 12 + } + ], + "123": [ + { + "x": 6, + "y": 12 + } + ], + "141": [ + { + "x": 7, + "y": 12 + } + ], + "147": [ + { + "x": 8, + "y": 12 + } + ], + "182": [ + { + "x": 9, + "y": 12 + } + ], + "187": [ + { + "x": 10, + "y": 12 + } + ], + "223": [ + { + "x": 11, + "y": 12 + } + ], + "228": [ + { + "x": 12, + "y": 12 + } + ], + "264": [ + { + "x": 13, + "y": 12 + } + ], + "269": [ + { + "x": 14, + "y": 12 + } + ], + "286": [ + { + "x": 15, + "y": 12 + } + ], + "291": [ + { + "x": 16, + "y": 12 + } + ], + "326": [ + { + "x": 17, + "y": 12 + } + ], + "344": [ + { + "x": 18, + "y": 12 + } + ], + "381": [ + { + "x": 19, + "y": 12 + } + ], + "399": [ + { + "x": 20, + "y": 12 + } + ], + "436": [ + { + "x": 21, + "y": 12 + } + ], + "454": [ + { + "x": 22, + "y": 12 + } + ], + "474": [ + { + "x": 23, + "y": 12 + } + ], + "488": [ + { + "x": 24, + "y": 12 + } + ], + "525": [ + { + "x": 25, + "y": 12 + } + ], + "542": [ + { + "x": 26, + "y": 12 + } + ], + "580": [ + { + "x": 27, + "y": 12 + } + ], + "597": [ + { + "x": 28, + "y": 12 + } + ], + "635": [ + { + "x": 29, + "y": 12 + } + ], + "652": [ + { + "x": 30, + "y": 12 + } + ], + "671": [ + { + "x": 31, + "y": 12 + } + ], + "684": [ + { + "x": 32, + "y": 12 + } + ], + "721": [ + { + "x": 33, + "y": 12 + } + ], + "739": [ + { + "x": 34, + "y": 12 + } + ], + "776": [ + { + "x": 35, + "y": 12 + } + ], + "794": [ + { + "x": 36, + "y": 12 + } + ], + "831": [ + { + "x": 37, + "y": 12 + } + ], + "849": [ + { + "x": 38, + "y": 12 + } + ], + "869": [ + { + "x": 39, + "y": 12 + } + ], + "883": [ + { + "x": 40, + "y": 12 + } + ], + "920": [ + { + "x": 41, + "y": 12 + } + ], + "937": [ + { + "x": 42, + "y": 12 + } + ], + "975": [ + { + "x": 43, + "y": 12 + } + ], + "992": [ + { + "x": 44, + "y": 12 + } + ], + "1030": [ + { + "x": 45, + "y": 12 + } + ], + "1047": [ + { + "x": 46, + "y": 12 + } + ], + "1066": [ + { + "x": 47, + "y": 12 + } + ], + "1079": [ + { + "x": 48, + "y": 12 + } + ], + "1116": [ + { + "x": 49, + "y": 12 + } + ], + "1134": [ + { + "x": 50, + "y": 12 + } + ], + "1171": [ + { + "x": 51, + "y": 12 + } + ], + "1189": [ + { + "x": 52, + "y": 12 + } + ], + "1226": [ + { + "x": 53, + "y": 12 + } + ], + "1244": [ + { + "x": 54, + "y": 12 + } + ], + "1264": [ + { + "x": 55, + "y": 12 + } + ], + "1278": [ + { + "x": 56, + "y": 12 + } + ], + "1315": [ + { + "x": 57, + "y": 12 + } + ], + "1332": [ + { + "x": 58, + "y": 12 + } + ], + "1370": [ + { + "x": 59, + "y": 12 + } + ], + "1387": [ + { + "x": 60, + "y": 12 + } + ], + "1425": [ + { + "x": 61, + "y": 12 + } + ], + "1442": [ + { + "x": 62, + "y": 12 + } + ], + "1459": [ + { + "x": 63, + "y": 12 + } + ], + "1464": [ + { + "x": 64, + "y": 12 + } + ], + "1499": [ + { + "x": 65, + "y": 12 + } + ], + "1505": [ + { + "x": 66, + "y": 12 + } + ], + "1540": [ + { + "x": 67, + "y": 12 + } + ], + "1546": [ + { + "x": 68, + "y": 12 + } + ], + "1581": [ + { + "x": 69, + "y": 12 + } + ], + "1587": [ + { + "x": 70, + "y": 12 + } + ], + "1605": [ + { + "x": 71, + "y": 12 + } + ], + "1611": [ + { + "x": 72, + "y": 12 + } + ], + "1646": [ + { + "x": 73, + "y": 12 + } + ], + "1651": [ + { + "x": 74, + "y": 12 + } + ], + "1687": [ + { + "x": 75, + "y": 12 + } + ], + "34": [ + { + "x": 1, + "y": 13 + } + ], + "75": [ + { + "x": 3, + "y": 13 + } + ], + "116": [ + { + "x": 5, + "y": 13 + } + ], + "181": [ + { + "x": 9, + "y": 13 + } + ], + "222": [ + { + "x": 11, + "y": 13 + } + ], + "263": [ + { + "x": 13, + "y": 13 + } + ], + "325": [ + { + "x": 17, + "y": 13 + } + ], + "380": [ + { + "x": 19, + "y": 13 + } + ], + "435": [ + { + "x": 21, + "y": 13 + } + ], + "524": [ + { + "x": 25, + "y": 13 + } + ], + "579": [ + { + "x": 27, + "y": 13 + } + ], + "634": [ + { + "x": 29, + "y": 13 + } + ], + "720": [ + { + "x": 33, + "y": 13 + } + ], + "775": [ + { + "x": 35, + "y": 13 + } + ], + "830": [ + { + "x": 37, + "y": 13 + } + ], + "919": [ + { + "x": 41, + "y": 13 + } + ], + "974": [ + { + "x": 43, + "y": 13 + } + ], + "1029": [ + { + "x": 45, + "y": 13 + } + ], + "1115": [ + { + "x": 49, + "y": 13 + } + ], + "1170": [ + { + "x": 51, + "y": 13 + } + ], + "1225": [ + { + "x": 53, + "y": 13 + } + ], + "1314": [ + { + "x": 57, + "y": 13 + } + ], + "1369": [ + { + "x": 59, + "y": 13 + } + ], + "1424": [ + { + "x": 61, + "y": 13 + } + ], + "1498": [ + { + "x": 65, + "y": 13 + } + ], + "1539": [ + { + "x": 67, + "y": 13 + } + ], + "1580": [ + { + "x": 69, + "y": 13 + } + ], + "1645": [ + { + "x": 73, + "y": 13 + } + ], + "1686": [ + { + "x": 75, + "y": 13 + } + ], + "33": [ + { + "x": 1, + "y": 14 + } + ], + "74": [ + { + "x": 3, + "y": 14 + } + ], + "115": [ + { + "x": 5, + "y": 14 + } + ], + "140": [ + { + "x": 7, + "y": 14 + } + ], + "180": [ + { + "x": 9, + "y": 14 + } + ], + "221": [ + { + "x": 11, + "y": 14 + } + ], + "262": [ + { + "x": 13, + "y": 14 + } + ], + "285": [ + { + "x": 15, + "y": 14 + } + ], + "324": [ + { + "x": 17, + "y": 14 + } + ], + "379": [ + { + "x": 19, + "y": 14 + } + ], + "434": [ + { + "x": 21, + "y": 14 + } + ], + "473": [ + { + "x": 23, + "y": 14 + } + ], + "523": [ + { + "x": 25, + "y": 14 + } + ], + "578": [ + { + "x": 27, + "y": 14 + } + ], + "633": [ + { + "x": 29, + "y": 14 + } + ], + "670": [ + { + "x": 31, + "y": 14 + } + ], + "719": [ + { + "x": 33, + "y": 14 + } + ], + "774": [ + { + "x": 35, + "y": 14 + } + ], + "829": [ + { + "x": 37, + "y": 14 + } + ], + "868": [ + { + "x": 39, + "y": 14 + } + ], + "918": [ + { + "x": 41, + "y": 14 + } + ], + "973": [ + { + "x": 43, + "y": 14 + } + ], + "1028": [ + { + "x": 45, + "y": 14 + } + ], + "1065": [ + { + "x": 47, + "y": 14 + } + ], + "1114": [ + { + "x": 49, + "y": 14 + } + ], + "1169": [ + { + "x": 51, + "y": 14 + } + ], + "1224": [ + { + "x": 53, + "y": 14 + } + ], + "1263": [ + { + "x": 55, + "y": 14 + } + ], + "1313": [ + { + "x": 57, + "y": 14 + } + ], + "1368": [ + { + "x": 59, + "y": 14 + } + ], + "1423": [ + { + "x": 61, + "y": 14 + } + ], + "1458": [ + { + "x": 63, + "y": 14 + } + ], + "1497": [ + { + "x": 65, + "y": 14 + } + ], + "1538": [ + { + "x": 67, + "y": 14 + } + ], + "1579": [ + { + "x": 69, + "y": 14 + } + ], + "1604": [ + { + "x": 71, + "y": 14 + } + ], + "1644": [ + { + "x": 73, + "y": 14 + } + ], + "1685": [ + { + "x": 75, + "y": 14 + } + ], + "32": [ + { + "x": 1, + "y": 15 + } + ], + "73": [ + { + "x": 3, + "y": 15 + } + ], + "114": [ + { + "x": 5, + "y": 15 + } + ], + "179": [ + { + "x": 9, + "y": 15 + } + ], + "220": [ + { + "x": 11, + "y": 15 + } + ], + "261": [ + { + "x": 13, + "y": 15 + } + ], + "323": [ + { + "x": 17, + "y": 15 + } + ], + "378": [ + { + "x": 19, + "y": 15 + } + ], + "433": [ + { + "x": 21, + "y": 15 + } + ], + "522": [ + { + "x": 25, + "y": 15 + } + ], + "577": [ + { + "x": 27, + "y": 15 + } + ], + "632": [ + { + "x": 29, + "y": 15 + } + ], + "718": [ + { + "x": 33, + "y": 15 + } + ], + "773": [ + { + "x": 35, + "y": 15 + } + ], + "828": [ + { + "x": 37, + "y": 15 + } + ], + "917": [ + { + "x": 41, + "y": 15 + } + ], + "972": [ + { + "x": 43, + "y": 15 + } + ], + "1027": [ + { + "x": 45, + "y": 15 + } + ], + "1113": [ + { + "x": 49, + "y": 15 + } + ], + "1168": [ + { + "x": 51, + "y": 15 + } + ], + "1223": [ + { + "x": 53, + "y": 15 + } + ], + "1312": [ + { + "x": 57, + "y": 15 + } + ], + "1367": [ + { + "x": 59, + "y": 15 + } + ], + "1422": [ + { + "x": 61, + "y": 15 + } + ], + "1496": [ + { + "x": 65, + "y": 15 + } + ], + "1537": [ + { + "x": 67, + "y": 15 + } + ], + "1578": [ + { + "x": 69, + "y": 15 + } + ], + "1643": [ + { + "x": 73, + "y": 15 + } + ], + "1684": [ + { + "x": 75, + "y": 15 + } + ], + "31": [ + { + "x": 1, + "y": 16 + } + ], + "72": [ + { + "x": 3, + "y": 16 + } + ], + "113": [ + { + "x": 5, + "y": 16 + } + ], + "139": [ + { + "x": 7, + "y": 16 + } + ], + "178": [ + { + "x": 9, + "y": 16 + } + ], + "219": [ + { + "x": 11, + "y": 16 + } + ], + "260": [ + { + "x": 13, + "y": 16 + } + ], + "284": [ + { + "x": 15, + "y": 16 + } + ], + "322": [ + { + "x": 17, + "y": 16 + } + ], + "377": [ + { + "x": 19, + "y": 16 + } + ], + "432": [ + { + "x": 21, + "y": 16 + } + ], + "472": [ + { + "x": 23, + "y": 16 + } + ], + "521": [ + { + "x": 25, + "y": 16 + } + ], + "576": [ + { + "x": 27, + "y": 16 + } + ], + "631": [ + { + "x": 29, + "y": 16 + } + ], + "669": [ + { + "x": 31, + "y": 16 + } + ], + "717": [ + { + "x": 33, + "y": 16 + } + ], + "772": [ + { + "x": 35, + "y": 16 + } + ], + "827": [ + { + "x": 37, + "y": 16 + } + ], + "867": [ + { + "x": 39, + "y": 16 + } + ], + "916": [ + { + "x": 41, + "y": 16 + } + ], + "971": [ + { + "x": 43, + "y": 16 + } + ], + "1026": [ + { + "x": 45, + "y": 16 + } + ], + "1064": [ + { + "x": 47, + "y": 16 + } + ], + "1112": [ + { + "x": 49, + "y": 16 + } + ], + "1167": [ + { + "x": 51, + "y": 16 + } + ], + "1222": [ + { + "x": 53, + "y": 16 + } + ], + "1262": [ + { + "x": 55, + "y": 16 + } + ], + "1311": [ + { + "x": 57, + "y": 16 + } + ], + "1366": [ + { + "x": 59, + "y": 16 + } + ], + "1421": [ + { + "x": 61, + "y": 16 + } + ], + "1457": [ + { + "x": 63, + "y": 16 + } + ], + "1495": [ + { + "x": 65, + "y": 16 + } + ], + "1536": [ + { + "x": 67, + "y": 16 + } + ], + "1577": [ + { + "x": 69, + "y": 16 + } + ], + "1603": [ + { + "x": 71, + "y": 16 + } + ], + "1642": [ + { + "x": 73, + "y": 16 + } + ], + "1683": [ + { + "x": 75, + "y": 16 + } + ], + "30": [ + { + "x": 1, + "y": 17 + } + ], + "71": [ + { + "x": 3, + "y": 17 + } + ], + "112": [ + { + "x": 5, + "y": 17 + } + ], + "138": [ + { + "x": 7, + "y": 17 + } + ], + "177": [ + { + "x": 9, + "y": 17 + } + ], + "218": [ + { + "x": 11, + "y": 17 + } + ], + "259": [ + { + "x": 13, + "y": 17 + } + ], + "283": [ + { + "x": 15, + "y": 17 + } + ], + "321": [ + { + "x": 17, + "y": 17 + } + ], + "376": [ + { + "x": 19, + "y": 17 + } + ], + "431": [ + { + "x": 21, + "y": 17 + } + ], + "471": [ + { + "x": 23, + "y": 17 + } + ], + "520": [ + { + "x": 25, + "y": 17 + } + ], + "575": [ + { + "x": 27, + "y": 17 + } + ], + "630": [ + { + "x": 29, + "y": 17 + } + ], + "668": [ + { + "x": 31, + "y": 17 + } + ], + "716": [ + { + "x": 33, + "y": 17 + } + ], + "771": [ + { + "x": 35, + "y": 17 + } + ], + "826": [ + { + "x": 37, + "y": 17 + } + ], + "866": [ + { + "x": 39, + "y": 17 + } + ], + "915": [ + { + "x": 41, + "y": 17 + } + ], + "970": [ + { + "x": 43, + "y": 17 + } + ], + "1025": [ + { + "x": 45, + "y": 17 + } + ], + "1063": [ + { + "x": 47, + "y": 17 + } + ], + "1111": [ + { + "x": 49, + "y": 17 + } + ], + "1166": [ + { + "x": 51, + "y": 17 + } + ], + "1221": [ + { + "x": 53, + "y": 17 + } + ], + "1261": [ + { + "x": 55, + "y": 17 + } + ], + "1310": [ + { + "x": 57, + "y": 17 + } + ], + "1365": [ + { + "x": 59, + "y": 17 + } + ], + "1420": [ + { + "x": 61, + "y": 17 + } + ], + "1456": [ + { + "x": 63, + "y": 17 + } + ], + "1494": [ + { + "x": 65, + "y": 17 + } + ], + "1535": [ + { + "x": 67, + "y": 17 + } + ], + "1576": [ + { + "x": 69, + "y": 17 + } + ], + "1602": [ + { + "x": 71, + "y": 17 + } + ], + "1641": [ + { + "x": 73, + "y": 17 + } + ], + "1682": [ + { + "x": 75, + "y": 17 + } + ], + "29": [ + { + "x": 1, + "y": 18 + } + ], + "70": [ + { + "x": 3, + "y": 18 + } + ], + "111": [ + { + "x": 5, + "y": 18 + } + ], + "137": [ + { + "x": 7, + "y": 18 + } + ], + "176": [ + { + "x": 9, + "y": 18 + } + ], + "217": [ + { + "x": 11, + "y": 18 + } + ], + "258": [ + { + "x": 13, + "y": 18 + } + ], + "282": [ + { + "x": 15, + "y": 18 + } + ], + "320": [ + { + "x": 17, + "y": 18 + } + ], + "375": [ + { + "x": 19, + "y": 18 + } + ], + "430": [ + { + "x": 21, + "y": 18 + } + ], + "470": [ + { + "x": 23, + "y": 18 + } + ], + "519": [ + { + "x": 25, + "y": 18 + } + ], + "574": [ + { + "x": 27, + "y": 18 + } + ], + "629": [ + { + "x": 29, + "y": 18 + } + ], + "667": [ + { + "x": 31, + "y": 18 + } + ], + "715": [ + { + "x": 33, + "y": 18 + } + ], + "770": [ + { + "x": 35, + "y": 18 + } + ], + "825": [ + { + "x": 37, + "y": 18 + } + ], + "865": [ + { + "x": 39, + "y": 18 + } + ], + "914": [ + { + "x": 41, + "y": 18 + } + ], + "969": [ + { + "x": 43, + "y": 18 + } + ], + "1024": [ + { + "x": 45, + "y": 18 + } + ], + "1062": [ + { + "x": 47, + "y": 18 + } + ], + "1110": [ + { + "x": 49, + "y": 18 + } + ], + "1165": [ + { + "x": 51, + "y": 18 + } + ], + "1220": [ + { + "x": 53, + "y": 18 + } + ], + "1260": [ + { + "x": 55, + "y": 18 + } + ], + "1309": [ + { + "x": 57, + "y": 18 + } + ], + "1364": [ + { + "x": 59, + "y": 18 + } + ], + "1419": [ + { + "x": 61, + "y": 18 + } + ], + "1455": [ + { + "x": 63, + "y": 18 + } + ], + "1493": [ + { + "x": 65, + "y": 18 + } + ], + "1534": [ + { + "x": 67, + "y": 18 + } + ], + "1575": [ + { + "x": 69, + "y": 18 + } + ], + "1601": [ + { + "x": 71, + "y": 18 + } + ], + "1640": [ + { + "x": 73, + "y": 18 + } + ], + "1681": [ + { + "x": 75, + "y": 18 + } + ], + "28": [ + { + "x": 1, + "y": 19 + } + ], + "69": [ + { + "x": 3, + "y": 19 + } + ], + "110": [ + { + "x": 5, + "y": 19 + } + ], + "175": [ + { + "x": 9, + "y": 19 + } + ], + "216": [ + { + "x": 11, + "y": 19 + } + ], + "257": [ + { + "x": 13, + "y": 19 + } + ], + "319": [ + { + "x": 17, + "y": 19 + } + ], + "374": [ + { + "x": 19, + "y": 19 + } + ], + "429": [ + { + "x": 21, + "y": 19 + } + ], + "518": [ + { + "x": 25, + "y": 19 + } + ], + "573": [ + { + "x": 27, + "y": 19 + } + ], + "628": [ + { + "x": 29, + "y": 19 + } + ], + "714": [ + { + "x": 33, + "y": 19 + } + ], + "769": [ + { + "x": 35, + "y": 19 + } + ], + "824": [ + { + "x": 37, + "y": 19 + } + ], + "913": [ + { + "x": 41, + "y": 19 + } + ], + "968": [ + { + "x": 43, + "y": 19 + } + ], + "1023": [ + { + "x": 45, + "y": 19 + } + ], + "1109": [ + { + "x": 49, + "y": 19 + } + ], + "1164": [ + { + "x": 51, + "y": 19 + } + ], + "1219": [ + { + "x": 53, + "y": 19 + } + ], + "1308": [ + { + "x": 57, + "y": 19 + } + ], + "1363": [ + { + "x": 59, + "y": 19 + } + ], + "1418": [ + { + "x": 61, + "y": 19 + } + ], + "1492": [ + { + "x": 65, + "y": 19 + } + ], + "1533": [ + { + "x": 67, + "y": 19 + } + ], + "1574": [ + { + "x": 69, + "y": 19 + } + ], + "1639": [ + { + "x": 73, + "y": 19 + } + ], + "1680": [ + { + "x": 75, + "y": 19 + } + ], + "27": [ + { + "x": 1, + "y": 20 + } + ], + "68": [ + { + "x": 3, + "y": 20 + } + ], + "109": [ + { + "x": 5, + "y": 20 + } + ], + "136": [ + { + "x": 7, + "y": 20 + } + ], + "174": [ + { + "x": 9, + "y": 20 + } + ], + "215": [ + { + "x": 11, + "y": 20 + } + ], + "256": [ + { + "x": 13, + "y": 20 + } + ], + "281": [ + { + "x": 15, + "y": 20 + } + ], + "318": [ + { + "x": 17, + "y": 20 + } + ], + "373": [ + { + "x": 19, + "y": 20 + } + ], + "428": [ + { + "x": 21, + "y": 20 + } + ], + "469": [ + { + "x": 23, + "y": 20 + } + ], + "517": [ + { + "x": 25, + "y": 20 + } + ], + "572": [ + { + "x": 27, + "y": 20 + } + ], + "627": [ + { + "x": 29, + "y": 20 + } + ], + "666": [ + { + "x": 31, + "y": 20 + } + ], + "713": [ + { + "x": 33, + "y": 20 + } + ], + "768": [ + { + "x": 35, + "y": 20 + } + ], + "823": [ + { + "x": 37, + "y": 20 + } + ], + "864": [ + { + "x": 39, + "y": 20 + } + ], + "912": [ + { + "x": 41, + "y": 20 + } + ], + "967": [ + { + "x": 43, + "y": 20 + } + ], + "1022": [ + { + "x": 45, + "y": 20 + } + ], + "1061": [ + { + "x": 47, + "y": 20 + } + ], + "1108": [ + { + "x": 49, + "y": 20 + } + ], + "1163": [ + { + "x": 51, + "y": 20 + } + ], + "1218": [ + { + "x": 53, + "y": 20 + } + ], + "1259": [ + { + "x": 55, + "y": 20 + } + ], + "1307": [ + { + "x": 57, + "y": 20 + } + ], + "1362": [ + { + "x": 59, + "y": 20 + } + ], + "1417": [ + { + "x": 61, + "y": 20 + } + ], + "1454": [ + { + "x": 63, + "y": 20 + } + ], + "1491": [ + { + "x": 65, + "y": 20 + } + ], + "1532": [ + { + "x": 67, + "y": 20 + } + ], + "1573": [ + { + "x": 69, + "y": 20 + } + ], + "1600": [ + { + "x": 71, + "y": 20 + } + ], + "1638": [ + { + "x": 73, + "y": 20 + } + ], + "1679": [ + { + "x": 75, + "y": 20 + } + ], + "26": [ + { + "x": 1, + "y": 21 + } + ], + "67": [ + { + "x": 3, + "y": 21 + } + ], + "108": [ + { + "x": 5, + "y": 21 + } + ], + "173": [ + { + "x": 9, + "y": 21 + } + ], + "214": [ + { + "x": 11, + "y": 21 + } + ], + "255": [ + { + "x": 13, + "y": 21 + } + ], + "317": [ + { + "x": 17, + "y": 21 + } + ], + "372": [ + { + "x": 19, + "y": 21 + } + ], + "427": [ + { + "x": 21, + "y": 21 + } + ], + "516": [ + { + "x": 25, + "y": 21 + } + ], + "571": [ + { + "x": 27, + "y": 21 + } + ], + "626": [ + { + "x": 29, + "y": 21 + } + ], + "712": [ + { + "x": 33, + "y": 21 + } + ], + "767": [ + { + "x": 35, + "y": 21 + } + ], + "822": [ + { + "x": 37, + "y": 21 + } + ], + "911": [ + { + "x": 41, + "y": 21 + } + ], + "966": [ + { + "x": 43, + "y": 21 + } + ], + "1021": [ + { + "x": 45, + "y": 21 + } + ], + "1107": [ + { + "x": 49, + "y": 21 + } + ], + "1162": [ + { + "x": 51, + "y": 21 + } + ], + "1217": [ + { + "x": 53, + "y": 21 + } + ], + "1306": [ + { + "x": 57, + "y": 21 + } + ], + "1361": [ + { + "x": 59, + "y": 21 + } + ], + "1416": [ + { + "x": 61, + "y": 21 + } + ], + "1490": [ + { + "x": 65, + "y": 21 + } + ], + "1531": [ + { + "x": 67, + "y": 21 + } + ], + "1572": [ + { + "x": 69, + "y": 21 + } + ], + "1637": [ + { + "x": 73, + "y": 21 + } + ], + "1678": [ + { + "x": 75, + "y": 21 + } + ], + "25": [ + { + "x": 1, + "y": 22 + } + ], + "40": [ + { + "x": 2, + "y": 22 + } + ], + "66": [ + { + "x": 3, + "y": 22 + } + ], + "81": [ + { + "x": 4, + "y": 22 + } + ], + "107": [ + { + "x": 5, + "y": 22 + } + ], + "122": [ + { + "x": 6, + "y": 22 + } + ], + "135": [ + { + "x": 7, + "y": 22 + } + ], + "146": [ + { + "x": 8, + "y": 22 + } + ], + "172": [ + { + "x": 9, + "y": 22 + } + ], + "186": [ + { + "x": 10, + "y": 22 + } + ], + "213": [ + { + "x": 11, + "y": 22 + } + ], + "227": [ + { + "x": 12, + "y": 22 + } + ], + "254": [ + { + "x": 13, + "y": 22 + } + ], + "268": [ + { + "x": 14, + "y": 22 + } + ], + "280": [ + { + "x": 15, + "y": 22 + } + ], + "290": [ + { + "x": 16, + "y": 22 + } + ], + "316": [ + { + "x": 17, + "y": 22 + } + ], + "343": [ + { + "x": 18, + "y": 22 + } + ], + "371": [ + { + "x": 19, + "y": 22 + } + ], + "398": [ + { + "x": 20, + "y": 22 + } + ], + "426": [ + { + "x": 21, + "y": 22 + } + ], + "453": [ + { + "x": 22, + "y": 22 + } + ], + "468": [ + { + "x": 23, + "y": 22 + } + ], + "487": [ + { + "x": 24, + "y": 22 + } + ], + "515": [ + { + "x": 25, + "y": 22 + } + ], + "541": [ + { + "x": 26, + "y": 22 + } + ], + "570": [ + { + "x": 27, + "y": 22 + } + ], + "596": [ + { + "x": 28, + "y": 22 + } + ], + "625": [ + { + "x": 29, + "y": 22 + } + ], + "651": [ + { + "x": 30, + "y": 22 + } + ], + "665": [ + { + "x": 31, + "y": 22 + } + ], + "683": [ + { + "x": 32, + "y": 22 + } + ], + "711": [ + { + "x": 33, + "y": 22 + } + ], + "738": [ + { + "x": 34, + "y": 22 + } + ], + "766": [ + { + "x": 35, + "y": 22 + } + ], + "793": [ + { + "x": 36, + "y": 22 + } + ], + "821": [ + { + "x": 37, + "y": 22 + } + ], + "848": [ + { + "x": 38, + "y": 22 + } + ], + "863": [ + { + "x": 39, + "y": 22 + } + ], + "882": [ + { + "x": 40, + "y": 22 + } + ], + "910": [ + { + "x": 41, + "y": 22 + } + ], + "936": [ + { + "x": 42, + "y": 22 + } + ], + "965": [ + { + "x": 43, + "y": 22 + } + ], + "991": [ + { + "x": 44, + "y": 22 + } + ], + "1020": [ + { + "x": 45, + "y": 22 + } + ], + "1046": [ + { + "x": 46, + "y": 22 + } + ], + "1060": [ + { + "x": 47, + "y": 22 + } + ], + "1078": [ + { + "x": 48, + "y": 22 + } + ], + "1106": [ + { + "x": 49, + "y": 22 + } + ], + "1133": [ + { + "x": 50, + "y": 22 + } + ], + "1161": [ + { + "x": 51, + "y": 22 + } + ], + "1188": [ + { + "x": 52, + "y": 22 + } + ], + "1216": [ + { + "x": 53, + "y": 22 + } + ], + "1243": [ + { + "x": 54, + "y": 22 + } + ], + "1258": [ + { + "x": 55, + "y": 22 + } + ], + "1277": [ + { + "x": 56, + "y": 22 + } + ], + "1305": [ + { + "x": 57, + "y": 22 + } + ], + "1331": [ + { + "x": 58, + "y": 22 + } + ], + "1360": [ + { + "x": 59, + "y": 22 + } + ], + "1386": [ + { + "x": 60, + "y": 22 + } + ], + "1415": [ + { + "x": 61, + "y": 22 + } + ], + "1441": [ + { + "x": 62, + "y": 22 + } + ], + "1453": [ + { + "x": 63, + "y": 22 + } + ], + "1463": [ + { + "x": 64, + "y": 22 + } + ], + "1489": [ + { + "x": 65, + "y": 22 + } + ], + "1504": [ + { + "x": 66, + "y": 22 + } + ], + "1530": [ + { + "x": 67, + "y": 22 + } + ], + "1545": [ + { + "x": 68, + "y": 22 + } + ], + "1571": [ + { + "x": 69, + "y": 22 + } + ], + "1586": [ + { + "x": 70, + "y": 22 + } + ], + "1599": [ + { + "x": 71, + "y": 22 + } + ], + "1610": [ + { + "x": 72, + "y": 22 + } + ], + "1636": [ + { + "x": 73, + "y": 22 + } + ], + "1650": [ + { + "x": 74, + "y": 22 + } + ], + "1677": [ + { + "x": 75, + "y": 22 + } + ], + "24": [ + { + "x": 1, + "y": 23 + } + ], + "65": [ + { + "x": 3, + "y": 23 + } + ], + "106": [ + { + "x": 5, + "y": 23 + } + ], + "171": [ + { + "x": 9, + "y": 23 + } + ], + "212": [ + { + "x": 11, + "y": 23 + } + ], + "253": [ + { + "x": 13, + "y": 23 + } + ], + "315": [ + { + "x": 17, + "y": 23 + } + ], + "370": [ + { + "x": 19, + "y": 23 + } + ], + "425": [ + { + "x": 21, + "y": 23 + } + ], + "514": [ + { + "x": 25, + "y": 23 + } + ], + "569": [ + { + "x": 27, + "y": 23 + } + ], + "624": [ + { + "x": 29, + "y": 23 + } + ], + "710": [ + { + "x": 33, + "y": 23 + } + ], + "765": [ + { + "x": 35, + "y": 23 + } + ], + "820": [ + { + "x": 37, + "y": 23 + } + ], + "909": [ + { + "x": 41, + "y": 23 + } + ], + "964": [ + { + "x": 43, + "y": 23 + } + ], + "1019": [ + { + "x": 45, + "y": 23 + } + ], + "1105": [ + { + "x": 49, + "y": 23 + } + ], + "1160": [ + { + "x": 51, + "y": 23 + } + ], + "1215": [ + { + "x": 53, + "y": 23 + } + ], + "1304": [ + { + "x": 57, + "y": 23 + } + ], + "1359": [ + { + "x": 59, + "y": 23 + } + ], + "1414": [ + { + "x": 61, + "y": 23 + } + ], + "1488": [ + { + "x": 65, + "y": 23 + } + ], + "1529": [ + { + "x": 67, + "y": 23 + } + ], + "1570": [ + { + "x": 69, + "y": 23 + } + ], + "1635": [ + { + "x": 73, + "y": 23 + } + ], + "1676": [ + { + "x": 75, + "y": 23 + } + ], + "23": [ + { + "x": 1, + "y": 24 + } + ], + "64": [ + { + "x": 3, + "y": 24 + } + ], + "105": [ + { + "x": 5, + "y": 24 + } + ], + "170": [ + { + "x": 9, + "y": 24 + } + ], + "211": [ + { + "x": 11, + "y": 24 + } + ], + "252": [ + { + "x": 13, + "y": 24 + } + ], + "314": [ + { + "x": 17, + "y": 24 + } + ], + "369": [ + { + "x": 19, + "y": 24 + } + ], + "424": [ + { + "x": 21, + "y": 24 + } + ], + "513": [ + { + "x": 25, + "y": 24 + } + ], + "568": [ + { + "x": 27, + "y": 24 + } + ], + "623": [ + { + "x": 29, + "y": 24 + } + ], + "709": [ + { + "x": 33, + "y": 24 + } + ], + "764": [ + { + "x": 35, + "y": 24 + } + ], + "819": [ + { + "x": 37, + "y": 24 + } + ], + "908": [ + { + "x": 41, + "y": 24 + } + ], + "963": [ + { + "x": 43, + "y": 24 + } + ], + "1018": [ + { + "x": 45, + "y": 24 + } + ], + "1104": [ + { + "x": 49, + "y": 24 + } + ], + "1159": [ + { + "x": 51, + "y": 24 + } + ], + "1214": [ + { + "x": 53, + "y": 24 + } + ], + "1303": [ + { + "x": 57, + "y": 24 + } + ], + "1358": [ + { + "x": 59, + "y": 24 + } + ], + "1413": [ + { + "x": 61, + "y": 24 + } + ], + "1487": [ + { + "x": 65, + "y": 24 + } + ], + "1528": [ + { + "x": 67, + "y": 24 + } + ], + "1569": [ + { + "x": 69, + "y": 24 + } + ], + "1634": [ + { + "x": 73, + "y": 24 + } + ], + "1675": [ + { + "x": 75, + "y": 24 + } + ], + "22": [ + { + "x": 1, + "y": 25 + } + ], + "63": [ + { + "x": 3, + "y": 25 + } + ], + "104": [ + { + "x": 5, + "y": 25 + } + ], + "169": [ + { + "x": 9, + "y": 25 + } + ], + "210": [ + { + "x": 11, + "y": 25 + } + ], + "251": [ + { + "x": 13, + "y": 25 + } + ], + "313": [ + { + "x": 17, + "y": 25 + } + ], + "368": [ + { + "x": 19, + "y": 25 + } + ], + "423": [ + { + "x": 21, + "y": 25 + } + ], + "512": [ + { + "x": 25, + "y": 25 + } + ], + "567": [ + { + "x": 27, + "y": 25 + } + ], + "622": [ + { + "x": 29, + "y": 25 + } + ], + "708": [ + { + "x": 33, + "y": 25 + } + ], + "763": [ + { + "x": 35, + "y": 25 + } + ], + "818": [ + { + "x": 37, + "y": 25 + } + ], + "907": [ + { + "x": 41, + "y": 25 + } + ], + "962": [ + { + "x": 43, + "y": 25 + } + ], + "1017": [ + { + "x": 45, + "y": 25 + } + ], + "1103": [ + { + "x": 49, + "y": 25 + } + ], + "1158": [ + { + "x": 51, + "y": 25 + } + ], + "1213": [ + { + "x": 53, + "y": 25 + } + ], + "1302": [ + { + "x": 57, + "y": 25 + } + ], + "1357": [ + { + "x": 59, + "y": 25 + } + ], + "1412": [ + { + "x": 61, + "y": 25 + } + ], + "1486": [ + { + "x": 65, + "y": 25 + } + ], + "1527": [ + { + "x": 67, + "y": 25 + } + ], + "1568": [ + { + "x": 69, + "y": 25 + } + ], + "1633": [ + { + "x": 73, + "y": 25 + } + ], + "1674": [ + { + "x": 75, + "y": 25 + } + ], + "21": [ + { + "x": 1, + "y": 26 + } + ], + "62": [ + { + "x": 3, + "y": 26 + } + ], + "103": [ + { + "x": 5, + "y": 26 + } + ], + "168": [ + { + "x": 9, + "y": 26 + } + ], + "209": [ + { + "x": 11, + "y": 26 + } + ], + "250": [ + { + "x": 13, + "y": 26 + } + ], + "312": [ + { + "x": 17, + "y": 26 + } + ], + "367": [ + { + "x": 19, + "y": 26 + } + ], + "422": [ + { + "x": 21, + "y": 26 + } + ], + "511": [ + { + "x": 25, + "y": 26 + } + ], + "566": [ + { + "x": 27, + "y": 26 + } + ], + "621": [ + { + "x": 29, + "y": 26 + } + ], + "707": [ + { + "x": 33, + "y": 26 + } + ], + "762": [ + { + "x": 35, + "y": 26 + } + ], + "817": [ + { + "x": 37, + "y": 26 + } + ], + "906": [ + { + "x": 41, + "y": 26 + } + ], + "961": [ + { + "x": 43, + "y": 26 + } + ], + "1016": [ + { + "x": 45, + "y": 26 + } + ], + "1102": [ + { + "x": 49, + "y": 26 + } + ], + "1157": [ + { + "x": 51, + "y": 26 + } + ], + "1212": [ + { + "x": 53, + "y": 26 + } + ], + "1301": [ + { + "x": 57, + "y": 26 + } + ], + "1356": [ + { + "x": 59, + "y": 26 + } + ], + "1411": [ + { + "x": 61, + "y": 26 + } + ], + "1485": [ + { + "x": 65, + "y": 26 + } + ], + "1526": [ + { + "x": 67, + "y": 26 + } + ], + "1567": [ + { + "x": 69, + "y": 26 + } + ], + "1632": [ + { + "x": 73, + "y": 26 + } + ], + "1673": [ + { + "x": 75, + "y": 26 + } + ], + "20": [ + { + "x": 1, + "y": 27 + } + ], + "61": [ + { + "x": 3, + "y": 27 + } + ], + "102": [ + { + "x": 5, + "y": 27 + } + ], + "167": [ + { + "x": 9, + "y": 27 + } + ], + "208": [ + { + "x": 11, + "y": 27 + } + ], + "249": [ + { + "x": 13, + "y": 27 + } + ], + "311": [ + { + "x": 17, + "y": 27 + } + ], + "366": [ + { + "x": 19, + "y": 27 + } + ], + "421": [ + { + "x": 21, + "y": 27 + } + ], + "510": [ + { + "x": 25, + "y": 27 + } + ], + "565": [ + { + "x": 27, + "y": 27 + } + ], + "620": [ + { + "x": 29, + "y": 27 + } + ], + "706": [ + { + "x": 33, + "y": 27 + } + ], + "761": [ + { + "x": 35, + "y": 27 + } + ], + "816": [ + { + "x": 37, + "y": 27 + } + ], + "905": [ + { + "x": 41, + "y": 27 + } + ], + "960": [ + { + "x": 43, + "y": 27 + } + ], + "1015": [ + { + "x": 45, + "y": 27 + } + ], + "1101": [ + { + "x": 49, + "y": 27 + } + ], + "1156": [ + { + "x": 51, + "y": 27 + } + ], + "1211": [ + { + "x": 53, + "y": 27 + } + ], + "1300": [ + { + "x": 57, + "y": 27 + } + ], + "1355": [ + { + "x": 59, + "y": 27 + } + ], + "1410": [ + { + "x": 61, + "y": 27 + } + ], + "1484": [ + { + "x": 65, + "y": 27 + } + ], + "1525": [ + { + "x": 67, + "y": 27 + } + ], + "1566": [ + { + "x": 69, + "y": 27 + } + ], + "1631": [ + { + "x": 73, + "y": 27 + } + ], + "1672": [ + { + "x": 75, + "y": 27 + } + ], + "19": [ + { + "x": 1, + "y": 28 + } + ], + "60": [ + { + "x": 3, + "y": 28 + } + ], + "101": [ + { + "x": 5, + "y": 28 + } + ], + "134": [ + { + "x": 7, + "y": 28 + } + ], + "166": [ + { + "x": 9, + "y": 28 + } + ], + "207": [ + { + "x": 11, + "y": 28 + } + ], + "248": [ + { + "x": 13, + "y": 28 + } + ], + "279": [ + { + "x": 15, + "y": 28 + } + ], + "310": [ + { + "x": 17, + "y": 28 + } + ], + "365": [ + { + "x": 19, + "y": 28 + } + ], + "420": [ + { + "x": 21, + "y": 28 + } + ], + "467": [ + { + "x": 23, + "y": 28 + } + ], + "509": [ + { + "x": 25, + "y": 28 + } + ], + "564": [ + { + "x": 27, + "y": 28 + } + ], + "619": [ + { + "x": 29, + "y": 28 + } + ], + "664": [ + { + "x": 31, + "y": 28 + } + ], + "705": [ + { + "x": 33, + "y": 28 + } + ], + "760": [ + { + "x": 35, + "y": 28 + } + ], + "815": [ + { + "x": 37, + "y": 28 + } + ], + "862": [ + { + "x": 39, + "y": 28 + } + ], + "904": [ + { + "x": 41, + "y": 28 + } + ], + "959": [ + { + "x": 43, + "y": 28 + } + ], + "1014": [ + { + "x": 45, + "y": 28 + } + ], + "1059": [ + { + "x": 47, + "y": 28 + } + ], + "1100": [ + { + "x": 49, + "y": 28 + } + ], + "1155": [ + { + "x": 51, + "y": 28 + } + ], + "1210": [ + { + "x": 53, + "y": 28 + } + ], + "1257": [ + { + "x": 55, + "y": 28 + } + ], + "1299": [ + { + "x": 57, + "y": 28 + } + ], + "1354": [ + { + "x": 59, + "y": 28 + } + ], + "1409": [ + { + "x": 61, + "y": 28 + } + ], + "1452": [ + { + "x": 63, + "y": 28 + } + ], + "1483": [ + { + "x": 65, + "y": 28 + } + ], + "1524": [ + { + "x": 67, + "y": 28 + } + ], + "1565": [ + { + "x": 69, + "y": 28 + } + ], + "1598": [ + { + "x": 71, + "y": 28 + } + ], + "1630": [ + { + "x": 73, + "y": 28 + } + ], + "1671": [ + { + "x": 75, + "y": 28 + } + ], + "18": [ + { + "x": 1, + "y": 29 + } + ], + "59": [ + { + "x": 3, + "y": 29 + } + ], + "100": [ + { + "x": 5, + "y": 29 + } + ], + "165": [ + { + "x": 9, + "y": 29 + } + ], + "206": [ + { + "x": 11, + "y": 29 + } + ], + "247": [ + { + "x": 13, + "y": 29 + } + ], + "309": [ + { + "x": 17, + "y": 29 + } + ], + "364": [ + { + "x": 19, + "y": 29 + } + ], + "419": [ + { + "x": 21, + "y": 29 + } + ], + "508": [ + { + "x": 25, + "y": 29 + } + ], + "563": [ + { + "x": 27, + "y": 29 + } + ], + "618": [ + { + "x": 29, + "y": 29 + } + ], + "704": [ + { + "x": 33, + "y": 29 + } + ], + "759": [ + { + "x": 35, + "y": 29 + } + ], + "814": [ + { + "x": 37, + "y": 29 + } + ], + "903": [ + { + "x": 41, + "y": 29 + } + ], + "958": [ + { + "x": 43, + "y": 29 + } + ], + "1013": [ + { + "x": 45, + "y": 29 + } + ], + "1099": [ + { + "x": 49, + "y": 29 + } + ], + "1154": [ + { + "x": 51, + "y": 29 + } + ], + "1209": [ + { + "x": 53, + "y": 29 + } + ], + "1298": [ + { + "x": 57, + "y": 29 + } + ], + "1353": [ + { + "x": 59, + "y": 29 + } + ], + "1408": [ + { + "x": 61, + "y": 29 + } + ], + "1482": [ + { + "x": 65, + "y": 29 + } + ], + "1523": [ + { + "x": 67, + "y": 29 + } + ], + "1564": [ + { + "x": 69, + "y": 29 + } + ], + "1629": [ + { + "x": 73, + "y": 29 + } + ], + "1670": [ + { + "x": 75, + "y": 29 + } + ], + "17": [ + { + "x": 1, + "y": 30 + } + ], + "58": [ + { + "x": 3, + "y": 30 + } + ], + "99": [ + { + "x": 5, + "y": 30 + } + ], + "133": [ + { + "x": 7, + "y": 30 + } + ], + "164": [ + { + "x": 9, + "y": 30 + } + ], + "205": [ + { + "x": 11, + "y": 30 + } + ], + "246": [ + { + "x": 13, + "y": 30 + } + ], + "278": [ + { + "x": 15, + "y": 30 + } + ], + "308": [ + { + "x": 17, + "y": 30 + } + ], + "363": [ + { + "x": 19, + "y": 30 + } + ], + "418": [ + { + "x": 21, + "y": 30 + } + ], + "466": [ + { + "x": 23, + "y": 30 + } + ], + "507": [ + { + "x": 25, + "y": 30 + } + ], + "562": [ + { + "x": 27, + "y": 30 + } + ], + "617": [ + { + "x": 29, + "y": 30 + } + ], + "663": [ + { + "x": 31, + "y": 30 + } + ], + "703": [ + { + "x": 33, + "y": 30 + } + ], + "758": [ + { + "x": 35, + "y": 30 + } + ], + "813": [ + { + "x": 37, + "y": 30 + } + ], + "861": [ + { + "x": 39, + "y": 30 + } + ], + "902": [ + { + "x": 41, + "y": 30 + } + ], + "957": [ + { + "x": 43, + "y": 30 + } + ], + "1012": [ + { + "x": 45, + "y": 30 + } + ], + "1058": [ + { + "x": 47, + "y": 30 + } + ], + "1098": [ + { + "x": 49, + "y": 30 + } + ], + "1153": [ + { + "x": 51, + "y": 30 + } + ], + "1208": [ + { + "x": 53, + "y": 30 + } + ], + "1256": [ + { + "x": 55, + "y": 30 + } + ], + "1297": [ + { + "x": 57, + "y": 30 + } + ], + "1352": [ + { + "x": 59, + "y": 30 + } + ], + "1407": [ + { + "x": 61, + "y": 30 + } + ], + "1451": [ + { + "x": 63, + "y": 30 + } + ], + "1481": [ + { + "x": 65, + "y": 30 + } + ], + "1522": [ + { + "x": 67, + "y": 30 + } + ], + "1563": [ + { + "x": 69, + "y": 30 + } + ], + "1597": [ + { + "x": 71, + "y": 30 + } + ], + "1628": [ + { + "x": 73, + "y": 30 + } + ], + "1669": [ + { + "x": 75, + "y": 30 + } + ], + "16": [ + { + "x": 1, + "y": 31 + } + ], + "57": [ + { + "x": 3, + "y": 31 + } + ], + "98": [ + { + "x": 5, + "y": 31 + } + ], + "163": [ + { + "x": 9, + "y": 31 + } + ], + "204": [ + { + "x": 11, + "y": 31 + } + ], + "245": [ + { + "x": 13, + "y": 31 + } + ], + "307": [ + { + "x": 17, + "y": 31 + } + ], + "362": [ + { + "x": 19, + "y": 31 + } + ], + "417": [ + { + "x": 21, + "y": 31 + } + ], + "506": [ + { + "x": 25, + "y": 31 + } + ], + "561": [ + { + "x": 27, + "y": 31 + } + ], + "616": [ + { + "x": 29, + "y": 31 + } + ], + "702": [ + { + "x": 33, + "y": 31 + } + ], + "757": [ + { + "x": 35, + "y": 31 + } + ], + "812": [ + { + "x": 37, + "y": 31 + } + ], + "901": [ + { + "x": 41, + "y": 31 + } + ], + "956": [ + { + "x": 43, + "y": 31 + } + ], + "1011": [ + { + "x": 45, + "y": 31 + } + ], + "1097": [ + { + "x": 49, + "y": 31 + } + ], + "1152": [ + { + "x": 51, + "y": 31 + } + ], + "1207": [ + { + "x": 53, + "y": 31 + } + ], + "1296": [ + { + "x": 57, + "y": 31 + } + ], + "1351": [ + { + "x": 59, + "y": 31 + } + ], + "1406": [ + { + "x": 61, + "y": 31 + } + ], + "1480": [ + { + "x": 65, + "y": 31 + } + ], + "1521": [ + { + "x": 67, + "y": 31 + } + ], + "1562": [ + { + "x": 69, + "y": 31 + } + ], + "1627": [ + { + "x": 73, + "y": 31 + } + ], + "1668": [ + { + "x": 75, + "y": 31 + } + ], + "15": [ + { + "x": 1, + "y": 32 + } + ], + "39": [ + { + "x": 2, + "y": 32 + } + ], + "56": [ + { + "x": 3, + "y": 32 + } + ], + "80": [ + { + "x": 4, + "y": 32 + } + ], + "97": [ + { + "x": 5, + "y": 32 + } + ], + "121": [ + { + "x": 6, + "y": 32 + } + ], + "132": [ + { + "x": 7, + "y": 32 + } + ], + "145": [ + { + "x": 8, + "y": 32 + } + ], + "162": [ + { + "x": 9, + "y": 32 + } + ], + "185": [ + { + "x": 10, + "y": 32 + } + ], + "203": [ + { + "x": 11, + "y": 32 + } + ], + "226": [ + { + "x": 12, + "y": 32 + } + ], + "244": [ + { + "x": 13, + "y": 32 + } + ], + "267": [ + { + "x": 14, + "y": 32 + } + ], + "277": [ + { + "x": 15, + "y": 32 + } + ], + "289": [ + { + "x": 16, + "y": 32 + } + ], + "306": [ + { + "x": 17, + "y": 32 + } + ], + "342": [ + { + "x": 18, + "y": 32 + } + ], + "361": [ + { + "x": 19, + "y": 32 + } + ], + "397": [ + { + "x": 20, + "y": 32 + } + ], + "416": [ + { + "x": 21, + "y": 32 + } + ], + "452": [ + { + "x": 22, + "y": 32 + } + ], + "465": [ + { + "x": 23, + "y": 32 + } + ], + "486": [ + { + "x": 24, + "y": 32 + } + ], + "505": [ + { + "x": 25, + "y": 32 + } + ], + "540": [ + { + "x": 26, + "y": 32 + } + ], + "560": [ + { + "x": 27, + "y": 32 + } + ], + "595": [ + { + "x": 28, + "y": 32 + } + ], + "615": [ + { + "x": 29, + "y": 32 + } + ], + "650": [ + { + "x": 30, + "y": 32 + } + ], + "662": [ + { + "x": 31, + "y": 32 + } + ], + "682": [ + { + "x": 32, + "y": 32 + } + ], + "701": [ + { + "x": 33, + "y": 32 + } + ], + "737": [ + { + "x": 34, + "y": 32 + } + ], + "756": [ + { + "x": 35, + "y": 32 + } + ], + "792": [ + { + "x": 36, + "y": 32 + } + ], + "811": [ + { + "x": 37, + "y": 32 + } + ], + "847": [ + { + "x": 38, + "y": 32 + } + ], + "860": [ + { + "x": 39, + "y": 32 + } + ], + "881": [ + { + "x": 40, + "y": 32 + } + ], + "900": [ + { + "x": 41, + "y": 32 + } + ], + "935": [ + { + "x": 42, + "y": 32 + } + ], + "955": [ + { + "x": 43, + "y": 32 + } + ], + "990": [ + { + "x": 44, + "y": 32 + } + ], + "1010": [ + { + "x": 45, + "y": 32 + } + ], + "1045": [ + { + "x": 46, + "y": 32 + } + ], + "1057": [ + { + "x": 47, + "y": 32 + } + ], + "1077": [ + { + "x": 48, + "y": 32 + } + ], + "1096": [ + { + "x": 49, + "y": 32 + } + ], + "1132": [ + { + "x": 50, + "y": 32 + } + ], + "1151": [ + { + "x": 51, + "y": 32 + } + ], + "1187": [ + { + "x": 52, + "y": 32 + } + ], + "1206": [ + { + "x": 53, + "y": 32 + } + ], + "1242": [ + { + "x": 54, + "y": 32 + } + ], + "1255": [ + { + "x": 55, + "y": 32 + } + ], + "1276": [ + { + "x": 56, + "y": 32 + } + ], + "1295": [ + { + "x": 57, + "y": 32 + } + ], + "1330": [ + { + "x": 58, + "y": 32 + } + ], + "1350": [ + { + "x": 59, + "y": 32 + } + ], + "1385": [ + { + "x": 60, + "y": 32 + } + ], + "1405": [ + { + "x": 61, + "y": 32 + } + ], + "1440": [ + { + "x": 62, + "y": 32 + } + ], + "1450": [ + { + "x": 63, + "y": 32 + } + ], + "1462": [ + { + "x": 64, + "y": 32 + } + ], + "1479": [ + { + "x": 65, + "y": 32 + } + ], + "1503": [ + { + "x": 66, + "y": 32 + } + ], + "1520": [ + { + "x": 67, + "y": 32 + } + ], + "1544": [ + { + "x": 68, + "y": 32 + } + ], + "1561": [ + { + "x": 69, + "y": 32 + } + ], + "1585": [ + { + "x": 70, + "y": 32 + } + ], + "1596": [ + { + "x": 71, + "y": 32 + } + ], + "1609": [ + { + "x": 72, + "y": 32 + } + ], + "1626": [ + { + "x": 73, + "y": 32 + } + ], + "1649": [ + { + "x": 74, + "y": 32 + } + ], + "1667": [ + { + "x": 75, + "y": 32 + } + ], + "1690": [ + { + "x": 76, + "y": 32 + } + ], + "1706": [ + { + "x": 77, + "y": 32 + } + ], + "14": [ + { + "x": 1, + "y": 33 + } + ], + "55": [ + { + "x": 3, + "y": 33 + } + ], + "96": [ + { + "x": 5, + "y": 33 + } + ], + "161": [ + { + "x": 9, + "y": 33 + } + ], + "202": [ + { + "x": 11, + "y": 33 + } + ], + "243": [ + { + "x": 13, + "y": 33 + } + ], + "305": [ + { + "x": 17, + "y": 33 + } + ], + "360": [ + { + "x": 19, + "y": 33 + } + ], + "415": [ + { + "x": 21, + "y": 33 + } + ], + "504": [ + { + "x": 25, + "y": 33 + } + ], + "559": [ + { + "x": 27, + "y": 33 + } + ], + "614": [ + { + "x": 29, + "y": 33 + } + ], + "700": [ + { + "x": 33, + "y": 33 + } + ], + "755": [ + { + "x": 35, + "y": 33 + } + ], + "810": [ + { + "x": 37, + "y": 33 + } + ], + "899": [ + { + "x": 41, + "y": 33 + } + ], + "954": [ + { + "x": 43, + "y": 33 + } + ], + "1009": [ + { + "x": 45, + "y": 33 + } + ], + "1095": [ + { + "x": 49, + "y": 33 + } + ], + "1150": [ + { + "x": 51, + "y": 33 + } + ], + "1205": [ + { + "x": 53, + "y": 33 + } + ], + "1294": [ + { + "x": 57, + "y": 33 + } + ], + "1349": [ + { + "x": 59, + "y": 33 + } + ], + "1404": [ + { + "x": 61, + "y": 33 + } + ], + "1478": [ + { + "x": 65, + "y": 33 + } + ], + "1519": [ + { + "x": 67, + "y": 33 + } + ], + "1560": [ + { + "x": 69, + "y": 33 + } + ], + "1625": [ + { + "x": 73, + "y": 33 + } + ], + "1666": [ + { + "x": 75, + "y": 33 + } + ], + "1705": [ + { + "x": 77, + "y": 33 + } + ], + "13": [ + { + "x": 1, + "y": 34 + } + ], + "54": [ + { + "x": 3, + "y": 34 + } + ], + "95": [ + { + "x": 5, + "y": 34 + } + ], + "131": [ + { + "x": 7, + "y": 34 + } + ], + "160": [ + { + "x": 9, + "y": 34 + } + ], + "201": [ + { + "x": 11, + "y": 34 + } + ], + "242": [ + { + "x": 13, + "y": 34 + } + ], + "276": [ + { + "x": 15, + "y": 34 + } + ], + "304": [ + { + "x": 17, + "y": 34 + } + ], + "359": [ + { + "x": 19, + "y": 34 + } + ], + "414": [ + { + "x": 21, + "y": 34 + } + ], + "464": [ + { + "x": 23, + "y": 34 + } + ], + "503": [ + { + "x": 25, + "y": 34 + } + ], + "558": [ + { + "x": 27, + "y": 34 + } + ], + "613": [ + { + "x": 29, + "y": 34 + } + ], + "661": [ + { + "x": 31, + "y": 34 + } + ], + "699": [ + { + "x": 33, + "y": 34 + } + ], + "754": [ + { + "x": 35, + "y": 34 + } + ], + "809": [ + { + "x": 37, + "y": 34 + } + ], + "859": [ + { + "x": 39, + "y": 34 + } + ], + "898": [ + { + "x": 41, + "y": 34 + } + ], + "953": [ + { + "x": 43, + "y": 34 + } + ], + "1008": [ + { + "x": 45, + "y": 34 + } + ], + "1056": [ + { + "x": 47, + "y": 34 + } + ], + "1094": [ + { + "x": 49, + "y": 34 + } + ], + "1149": [ + { + "x": 51, + "y": 34 + } + ], + "1204": [ + { + "x": 53, + "y": 34 + } + ], + "1254": [ + { + "x": 55, + "y": 34 + } + ], + "1293": [ + { + "x": 57, + "y": 34 + } + ], + "1348": [ + { + "x": 59, + "y": 34 + } + ], + "1403": [ + { + "x": 61, + "y": 34 + } + ], + "1449": [ + { + "x": 63, + "y": 34 + } + ], + "1477": [ + { + "x": 65, + "y": 34 + } + ], + "1518": [ + { + "x": 67, + "y": 34 + } + ], + "1559": [ + { + "x": 69, + "y": 34 + } + ], + "1595": [ + { + "x": 71, + "y": 34 + } + ], + "1624": [ + { + "x": 73, + "y": 34 + } + ], + "1665": [ + { + "x": 75, + "y": 34 + } + ], + "1704": [ + { + "x": 77, + "y": 34 + } + ], + "12": [ + { + "x": 1, + "y": 35 + } + ], + "53": [ + { + "x": 3, + "y": 35 + } + ], + "94": [ + { + "x": 5, + "y": 35 + } + ], + "159": [ + { + "x": 9, + "y": 35 + } + ], + "200": [ + { + "x": 11, + "y": 35 + } + ], + "241": [ + { + "x": 13, + "y": 35 + } + ], + "303": [ + { + "x": 17, + "y": 35 + } + ], + "358": [ + { + "x": 19, + "y": 35 + } + ], + "413": [ + { + "x": 21, + "y": 35 + } + ], + "502": [ + { + "x": 25, + "y": 35 + } + ], + "557": [ + { + "x": 27, + "y": 35 + } + ], + "612": [ + { + "x": 29, + "y": 35 + } + ], + "698": [ + { + "x": 33, + "y": 35 + } + ], + "753": [ + { + "x": 35, + "y": 35 + } + ], + "808": [ + { + "x": 37, + "y": 35 + } + ], + "897": [ + { + "x": 41, + "y": 35 + } + ], + "952": [ + { + "x": 43, + "y": 35 + } + ], + "1007": [ + { + "x": 45, + "y": 35 + } + ], + "1093": [ + { + "x": 49, + "y": 35 + } + ], + "1148": [ + { + "x": 51, + "y": 35 + } + ], + "1203": [ + { + "x": 53, + "y": 35 + } + ], + "1292": [ + { + "x": 57, + "y": 35 + } + ], + "1347": [ + { + "x": 59, + "y": 35 + } + ], + "1402": [ + { + "x": 61, + "y": 35 + } + ], + "1476": [ + { + "x": 65, + "y": 35 + } + ], + "1517": [ + { + "x": 67, + "y": 35 + } + ], + "1558": [ + { + "x": 69, + "y": 35 + } + ], + "1623": [ + { + "x": 73, + "y": 35 + } + ], + "1664": [ + { + "x": 75, + "y": 35 + } + ], + "1703": [ + { + "x": 77, + "y": 35 + } + ], + "11": [ + { + "x": 1, + "y": 36 + } + ], + "52": [ + { + "x": 3, + "y": 36 + } + ], + "93": [ + { + "x": 5, + "y": 36 + } + ], + "130": [ + { + "x": 7, + "y": 36 + } + ], + "158": [ + { + "x": 9, + "y": 36 + } + ], + "199": [ + { + "x": 11, + "y": 36 + } + ], + "240": [ + { + "x": 13, + "y": 36 + } + ], + "275": [ + { + "x": 15, + "y": 36 + } + ], + "302": [ + { + "x": 17, + "y": 36 + } + ], + "357": [ + { + "x": 19, + "y": 36 + } + ], + "412": [ + { + "x": 21, + "y": 36 + } + ], + "463": [ + { + "x": 23, + "y": 36 + } + ], + "501": [ + { + "x": 25, + "y": 36 + } + ], + "556": [ + { + "x": 27, + "y": 36 + } + ], + "611": [ + { + "x": 29, + "y": 36 + } + ], + "660": [ + { + "x": 31, + "y": 36 + } + ], + "697": [ + { + "x": 33, + "y": 36 + } + ], + "752": [ + { + "x": 35, + "y": 36 + } + ], + "807": [ + { + "x": 37, + "y": 36 + } + ], + "858": [ + { + "x": 39, + "y": 36 + } + ], + "896": [ + { + "x": 41, + "y": 36 + } + ], + "951": [ + { + "x": 43, + "y": 36 + } + ], + "1006": [ + { + "x": 45, + "y": 36 + } + ], + "1055": [ + { + "x": 47, + "y": 36 + } + ], + "1092": [ + { + "x": 49, + "y": 36 + } + ], + "1147": [ + { + "x": 51, + "y": 36 + } + ], + "1202": [ + { + "x": 53, + "y": 36 + } + ], + "1253": [ + { + "x": 55, + "y": 36 + } + ], + "1291": [ + { + "x": 57, + "y": 36 + } + ], + "1346": [ + { + "x": 59, + "y": 36 + } + ], + "1401": [ + { + "x": 61, + "y": 36 + } + ], + "1448": [ + { + "x": 63, + "y": 36 + } + ], + "1475": [ + { + "x": 65, + "y": 36 + } + ], + "1516": [ + { + "x": 67, + "y": 36 + } + ], + "1557": [ + { + "x": 69, + "y": 36 + } + ], + "1594": [ + { + "x": 71, + "y": 36 + } + ], + "1622": [ + { + "x": 73, + "y": 36 + } + ], + "1663": [ + { + "x": 75, + "y": 36 + } + ], + "1702": [ + { + "x": 77, + "y": 36 + } + ], + "10": [ + { + "x": 1, + "y": 37 + } + ], + "51": [ + { + "x": 3, + "y": 37 + } + ], + "92": [ + { + "x": 5, + "y": 37 + } + ], + "129": [ + { + "x": 7, + "y": 37 + } + ], + "157": [ + { + "x": 9, + "y": 37 + } + ], + "198": [ + { + "x": 11, + "y": 37 + } + ], + "239": [ + { + "x": 13, + "y": 37 + } + ], + "274": [ + { + "x": 15, + "y": 37 + } + ], + "301": [ + { + "x": 17, + "y": 37 + } + ], + "356": [ + { + "x": 19, + "y": 37 + } + ], + "411": [ + { + "x": 21, + "y": 37 + } + ], + "462": [ + { + "x": 23, + "y": 37 + } + ], + "500": [ + { + "x": 25, + "y": 37 + } + ], + "555": [ + { + "x": 27, + "y": 37 + } + ], + "610": [ + { + "x": 29, + "y": 37 + } + ], + "659": [ + { + "x": 31, + "y": 37 + } + ], + "696": [ + { + "x": 33, + "y": 37 + } + ], + "751": [ + { + "x": 35, + "y": 37 + } + ], + "806": [ + { + "x": 37, + "y": 37 + } + ], + "857": [ + { + "x": 39, + "y": 37 + } + ], + "895": [ + { + "x": 41, + "y": 37 + } + ], + "950": [ + { + "x": 43, + "y": 37 + } + ], + "1005": [ + { + "x": 45, + "y": 37 + } + ], + "1054": [ + { + "x": 47, + "y": 37 + } + ], + "1091": [ + { + "x": 49, + "y": 37 + } + ], + "1146": [ + { + "x": 51, + "y": 37 + } + ], + "1201": [ + { + "x": 53, + "y": 37 + } + ], + "1252": [ + { + "x": 55, + "y": 37 + } + ], + "1290": [ + { + "x": 57, + "y": 37 + } + ], + "1345": [ + { + "x": 59, + "y": 37 + } + ], + "1400": [ + { + "x": 61, + "y": 37 + } + ], + "1447": [ + { + "x": 63, + "y": 37 + } + ], + "1474": [ + { + "x": 65, + "y": 37 + } + ], + "1515": [ + { + "x": 67, + "y": 37 + } + ], + "1556": [ + { + "x": 69, + "y": 37 + } + ], + "1593": [ + { + "x": 71, + "y": 37 + } + ], + "1621": [ + { + "x": 73, + "y": 37 + } + ], + "1662": [ + { + "x": 75, + "y": 37 + } + ], + "1701": [ + { + "x": 77, + "y": 37 + } + ], + "9": [ + { + "x": 1, + "y": 38 + } + ], + "50": [ + { + "x": 3, + "y": 38 + } + ], + "91": [ + { + "x": 5, + "y": 38 + } + ], + "128": [ + { + "x": 7, + "y": 38 + } + ], + "156": [ + { + "x": 9, + "y": 38 + } + ], + "197": [ + { + "x": 11, + "y": 38 + } + ], + "238": [ + { + "x": 13, + "y": 38 + } + ], + "273": [ + { + "x": 15, + "y": 38 + } + ], + "300": [ + { + "x": 17, + "y": 38 + } + ], + "355": [ + { + "x": 19, + "y": 38 + } + ], + "410": [ + { + "x": 21, + "y": 38 + } + ], + "461": [ + { + "x": 23, + "y": 38 + } + ], + "499": [ + { + "x": 25, + "y": 38 + } + ], + "554": [ + { + "x": 27, + "y": 38 + } + ], + "609": [ + { + "x": 29, + "y": 38 + } + ], + "658": [ + { + "x": 31, + "y": 38 + } + ], + "695": [ + { + "x": 33, + "y": 38 + } + ], + "750": [ + { + "x": 35, + "y": 38 + } + ], + "805": [ + { + "x": 37, + "y": 38 + } + ], + "856": [ + { + "x": 39, + "y": 38 + } + ], + "894": [ + { + "x": 41, + "y": 38 + } + ], + "949": [ + { + "x": 43, + "y": 38 + } + ], + "1004": [ + { + "x": 45, + "y": 38 + } + ], + "1053": [ + { + "x": 47, + "y": 38 + } + ], + "1090": [ + { + "x": 49, + "y": 38 + } + ], + "1145": [ + { + "x": 51, + "y": 38 + } + ], + "1200": [ + { + "x": 53, + "y": 38 + } + ], + "1251": [ + { + "x": 55, + "y": 38 + } + ], + "1289": [ + { + "x": 57, + "y": 38 + } + ], + "1344": [ + { + "x": 59, + "y": 38 + } + ], + "1399": [ + { + "x": 61, + "y": 38 + } + ], + "1446": [ + { + "x": 63, + "y": 38 + } + ], + "1473": [ + { + "x": 65, + "y": 38 + } + ], + "1514": [ + { + "x": 67, + "y": 38 + } + ], + "1555": [ + { + "x": 69, + "y": 38 + } + ], + "1592": [ + { + "x": 71, + "y": 38 + } + ], + "1620": [ + { + "x": 73, + "y": 38 + } + ], + "1661": [ + { + "x": 75, + "y": 38 + } + ], + "1700": [ + { + "x": 77, + "y": 38 + } + ], + "8": [ + { + "x": 1, + "y": 39 + } + ], + "49": [ + { + "x": 3, + "y": 39 + } + ], + "90": [ + { + "x": 5, + "y": 39 + } + ], + "155": [ + { + "x": 9, + "y": 39 + } + ], + "196": [ + { + "x": 11, + "y": 39 + } + ], + "237": [ + { + "x": 13, + "y": 39 + } + ], + "299": [ + { + "x": 17, + "y": 39 + } + ], + "354": [ + { + "x": 19, + "y": 39 + } + ], + "409": [ + { + "x": 21, + "y": 39 + } + ], + "498": [ + { + "x": 25, + "y": 39 + } + ], + "553": [ + { + "x": 27, + "y": 39 + } + ], + "608": [ + { + "x": 29, + "y": 39 + } + ], + "694": [ + { + "x": 33, + "y": 39 + } + ], + "749": [ + { + "x": 35, + "y": 39 + } + ], + "804": [ + { + "x": 37, + "y": 39 + } + ], + "893": [ + { + "x": 41, + "y": 39 + } + ], + "948": [ + { + "x": 43, + "y": 39 + } + ], + "1003": [ + { + "x": 45, + "y": 39 + } + ], + "1089": [ + { + "x": 49, + "y": 39 + } + ], + "1144": [ + { + "x": 51, + "y": 39 + } + ], + "1199": [ + { + "x": 53, + "y": 39 + } + ], + "1288": [ + { + "x": 57, + "y": 39 + } + ], + "1343": [ + { + "x": 59, + "y": 39 + } + ], + "1398": [ + { + "x": 61, + "y": 39 + } + ], + "1472": [ + { + "x": 65, + "y": 39 + } + ], + "1513": [ + { + "x": 67, + "y": 39 + } + ], + "1554": [ + { + "x": 69, + "y": 39 + } + ], + "1619": [ + { + "x": 73, + "y": 39 + } + ], + "1660": [ + { + "x": 75, + "y": 39 + } + ], + "1699": [ + { + "x": 77, + "y": 39 + } + ], + "7": [ + { + "x": 1, + "y": 40 + } + ], + "48": [ + { + "x": 3, + "y": 40 + } + ], + "89": [ + { + "x": 5, + "y": 40 + } + ], + "127": [ + { + "x": 7, + "y": 40 + } + ], + "154": [ + { + "x": 9, + "y": 40 + } + ], + "195": [ + { + "x": 11, + "y": 40 + } + ], + "236": [ + { + "x": 13, + "y": 40 + } + ], + "272": [ + { + "x": 15, + "y": 40 + } + ], + "298": [ + { + "x": 17, + "y": 40 + } + ], + "353": [ + { + "x": 19, + "y": 40 + } + ], + "408": [ + { + "x": 21, + "y": 40 + } + ], + "460": [ + { + "x": 23, + "y": 40 + } + ], + "497": [ + { + "x": 25, + "y": 40 + } + ], + "552": [ + { + "x": 27, + "y": 40 + } + ], + "607": [ + { + "x": 29, + "y": 40 + } + ], + "657": [ + { + "x": 31, + "y": 40 + } + ], + "693": [ + { + "x": 33, + "y": 40 + } + ], + "748": [ + { + "x": 35, + "y": 40 + } + ], + "803": [ + { + "x": 37, + "y": 40 + } + ], + "855": [ + { + "x": 39, + "y": 40 + } + ], + "892": [ + { + "x": 41, + "y": 40 + } + ], + "947": [ + { + "x": 43, + "y": 40 + } + ], + "1002": [ + { + "x": 45, + "y": 40 + } + ], + "1052": [ + { + "x": 47, + "y": 40 + } + ], + "1088": [ + { + "x": 49, + "y": 40 + } + ], + "1143": [ + { + "x": 51, + "y": 40 + } + ], + "1198": [ + { + "x": 53, + "y": 40 + } + ], + "1250": [ + { + "x": 55, + "y": 40 + } + ], + "1287": [ + { + "x": 57, + "y": 40 + } + ], + "1342": [ + { + "x": 59, + "y": 40 + } + ], + "1397": [ + { + "x": 61, + "y": 40 + } + ], + "1445": [ + { + "x": 63, + "y": 40 + } + ], + "1471": [ + { + "x": 65, + "y": 40 + } + ], + "1512": [ + { + "x": 67, + "y": 40 + } + ], + "1553": [ + { + "x": 69, + "y": 40 + } + ], + "1591": [ + { + "x": 71, + "y": 40 + } + ], + "1618": [ + { + "x": 73, + "y": 40 + } + ], + "1659": [ + { + "x": 75, + "y": 40 + } + ], + "1698": [ + { + "x": 77, + "y": 40 + } + ], + "6": [ + { + "x": 1, + "y": 41 + } + ], + "47": [ + { + "x": 3, + "y": 41 + } + ], + "88": [ + { + "x": 5, + "y": 41 + } + ], + "153": [ + { + "x": 9, + "y": 41 + } + ], + "194": [ + { + "x": 11, + "y": 41 + } + ], + "235": [ + { + "x": 13, + "y": 41 + } + ], + "297": [ + { + "x": 17, + "y": 41 + } + ], + "352": [ + { + "x": 19, + "y": 41 + } + ], + "407": [ + { + "x": 21, + "y": 41 + } + ], + "496": [ + { + "x": 25, + "y": 41 + } + ], + "551": [ + { + "x": 27, + "y": 41 + } + ], + "606": [ + { + "x": 29, + "y": 41 + } + ], + "692": [ + { + "x": 33, + "y": 41 + } + ], + "747": [ + { + "x": 35, + "y": 41 + } + ], + "802": [ + { + "x": 37, + "y": 41 + } + ], + "891": [ + { + "x": 41, + "y": 41 + } + ], + "946": [ + { + "x": 43, + "y": 41 + } + ], + "1001": [ + { + "x": 45, + "y": 41 + } + ], + "1087": [ + { + "x": 49, + "y": 41 + } + ], + "1142": [ + { + "x": 51, + "y": 41 + } + ], + "1197": [ + { + "x": 53, + "y": 41 + } + ], + "1286": [ + { + "x": 57, + "y": 41 + } + ], + "1341": [ + { + "x": 59, + "y": 41 + } + ], + "1396": [ + { + "x": 61, + "y": 41 + } + ], + "1470": [ + { + "x": 65, + "y": 41 + } + ], + "1511": [ + { + "x": 67, + "y": 41 + } + ], + "1552": [ + { + "x": 69, + "y": 41 + } + ], + "1617": [ + { + "x": 73, + "y": 41 + } + ], + "1658": [ + { + "x": 75, + "y": 41 + } + ], + "1697": [ + { + "x": 77, + "y": 41 + } + ], + "5": [ + { + "x": 1, + "y": 42 + } + ], + "38": [ + { + "x": 2, + "y": 42 + } + ], + "46": [ + { + "x": 3, + "y": 42 + } + ], + "79": [ + { + "x": 4, + "y": 42 + } + ], + "87": [ + { + "x": 5, + "y": 42 + } + ], + "120": [ + { + "x": 6, + "y": 42 + } + ], + "126": [ + { + "x": 7, + "y": 42 + } + ], + "144": [ + { + "x": 8, + "y": 42 + } + ], + "152": [ + { + "x": 9, + "y": 42 + } + ], + "184": [ + { + "x": 10, + "y": 42 + } + ], + "193": [ + { + "x": 11, + "y": 42 + } + ], + "225": [ + { + "x": 12, + "y": 42 + } + ], + "234": [ + { + "x": 13, + "y": 42 + } + ], + "266": [ + { + "x": 14, + "y": 42 + } + ], + "271": [ + { + "x": 15, + "y": 42 + } + ], + "288": [ + { + "x": 16, + "y": 42 + } + ], + "296": [ + { + "x": 17, + "y": 42 + } + ], + "341": [ + { + "x": 18, + "y": 42 + } + ], + "351": [ + { + "x": 19, + "y": 42 + } + ], + "396": [ + { + "x": 20, + "y": 42 + } + ], + "406": [ + { + "x": 21, + "y": 42 + } + ], + "451": [ + { + "x": 22, + "y": 42 + } + ], + "459": [ + { + "x": 23, + "y": 42 + } + ], + "485": [ + { + "x": 24, + "y": 42 + } + ], + "495": [ + { + "x": 25, + "y": 42 + } + ], + "539": [ + { + "x": 26, + "y": 42 + } + ], + "550": [ + { + "x": 27, + "y": 42 + } + ], + "594": [ + { + "x": 28, + "y": 42 + } + ], + "605": [ + { + "x": 29, + "y": 42 + } + ], + "649": [ + { + "x": 30, + "y": 42 + } + ], + "656": [ + { + "x": 31, + "y": 42 + } + ], + "681": [ + { + "x": 32, + "y": 42 + } + ], + "691": [ + { + "x": 33, + "y": 42 + } + ], + "736": [ + { + "x": 34, + "y": 42 + } + ], + "746": [ + { + "x": 35, + "y": 42 + } + ], + "791": [ + { + "x": 36, + "y": 42 + } + ], + "801": [ + { + "x": 37, + "y": 42 + } + ], + "846": [ + { + "x": 38, + "y": 42 + } + ], + "854": [ + { + "x": 39, + "y": 42 + } + ], + "880": [ + { + "x": 40, + "y": 42 + } + ], + "890": [ + { + "x": 41, + "y": 42 + } + ], + "934": [ + { + "x": 42, + "y": 42 + } + ], + "945": [ + { + "x": 43, + "y": 42 + } + ], + "989": [ + { + "x": 44, + "y": 42 + } + ], + "1000": [ + { + "x": 45, + "y": 42 + } + ], + "1044": [ + { + "x": 46, + "y": 42 + } + ], + "1051": [ + { + "x": 47, + "y": 42 + } + ], + "1076": [ + { + "x": 48, + "y": 42 + } + ], + "1086": [ + { + "x": 49, + "y": 42 + } + ], + "1131": [ + { + "x": 50, + "y": 42 + } + ], + "1141": [ + { + "x": 51, + "y": 42 + } + ], + "1186": [ + { + "x": 52, + "y": 42 + } + ], + "1196": [ + { + "x": 53, + "y": 42 + } + ], + "1241": [ + { + "x": 54, + "y": 42 + } + ], + "1249": [ + { + "x": 55, + "y": 42 + } + ], + "1275": [ + { + "x": 56, + "y": 42 + } + ], + "1285": [ + { + "x": 57, + "y": 42 + } + ], + "1329": [ + { + "x": 58, + "y": 42 + } + ], + "1340": [ + { + "x": 59, + "y": 42 + } + ], + "1384": [ + { + "x": 60, + "y": 42 + } + ], + "1395": [ + { + "x": 61, + "y": 42 + } + ], + "1439": [ + { + "x": 62, + "y": 42 + } + ], + "1444": [ + { + "x": 63, + "y": 42 + } + ], + "1461": [ + { + "x": 64, + "y": 42 + } + ], + "1469": [ + { + "x": 65, + "y": 42 + } + ], + "1502": [ + { + "x": 66, + "y": 42 + } + ], + "1510": [ + { + "x": 67, + "y": 42 + } + ], + "1543": [ + { + "x": 68, + "y": 42 + } + ], + "1551": [ + { + "x": 69, + "y": 42 + } + ], + "1584": [ + { + "x": 70, + "y": 42 + } + ], + "1590": [ + { + "x": 71, + "y": 42 + } + ], + "1608": [ + { + "x": 72, + "y": 42 + } + ], + "1616": [ + { + "x": 73, + "y": 42 + } + ], + "1648": [ + { + "x": 74, + "y": 42 + } + ], + "1657": [ + { + "x": 75, + "y": 42 + } + ], + "1689": [ + { + "x": 76, + "y": 42 + } + ], + "1696": [ + { + "x": 77, + "y": 42 + } + ], + "4": [ + { + "x": 1, + "y": 43 + } + ], + "45": [ + { + "x": 3, + "y": 43 + } + ], + "86": [ + { + "x": 5, + "y": 43 + } + ], + "151": [ + { + "x": 9, + "y": 43 + } + ], + "192": [ + { + "x": 11, + "y": 43 + } + ], + "233": [ + { + "x": 13, + "y": 43 + } + ], + "295": [ + { + "x": 17, + "y": 43 + } + ], + "350": [ + { + "x": 19, + "y": 43 + } + ], + "405": [ + { + "x": 21, + "y": 43 + } + ], + "494": [ + { + "x": 25, + "y": 43 + } + ], + "549": [ + { + "x": 27, + "y": 43 + } + ], + "604": [ + { + "x": 29, + "y": 43 + } + ], + "690": [ + { + "x": 33, + "y": 43 + } + ], + "745": [ + { + "x": 35, + "y": 43 + } + ], + "800": [ + { + "x": 37, + "y": 43 + } + ], + "889": [ + { + "x": 41, + "y": 43 + } + ], + "944": [ + { + "x": 43, + "y": 43 + } + ], + "999": [ + { + "x": 45, + "y": 43 + } + ], + "1085": [ + { + "x": 49, + "y": 43 + } + ], + "1140": [ + { + "x": 51, + "y": 43 + } + ], + "1195": [ + { + "x": 53, + "y": 43 + } + ], + "1284": [ + { + "x": 57, + "y": 43 + } + ], + "1339": [ + { + "x": 59, + "y": 43 + } + ], + "1394": [ + { + "x": 61, + "y": 43 + } + ], + "1468": [ + { + "x": 65, + "y": 43 + } + ], + "1509": [ + { + "x": 67, + "y": 43 + } + ], + "1550": [ + { + "x": 69, + "y": 43 + } + ], + "1615": [ + { + "x": 73, + "y": 43 + } + ], + "1656": [ + { + "x": 75, + "y": 43 + } + ], + "1695": [ + { + "x": 77, + "y": 43 + } + ], + "3": [ + { + "x": 1, + "y": 44 + } + ], + "37": [ + { + "x": 2, + "y": 44 + } + ], + "44": [ + { + "x": 3, + "y": 44 + } + ], + "78": [ + { + "x": 4, + "y": 44 + } + ], + "85": [ + { + "x": 5, + "y": 44 + } + ], + "119": [ + { + "x": 6, + "y": 44 + } + ], + "125": [ + { + "x": 7, + "y": 44 + } + ], + "143": [ + { + "x": 8, + "y": 44 + } + ], + "150": [ + { + "x": 9, + "y": 44 + } + ], + "183": [ + { + "x": 10, + "y": 44 + } + ], + "191": [ + { + "x": 11, + "y": 44 + } + ], + "224": [ + { + "x": 12, + "y": 44 + } + ], + "232": [ + { + "x": 13, + "y": 44 + } + ], + "265": [ + { + "x": 14, + "y": 44 + } + ], + "270": [ + { + "x": 15, + "y": 44 + } + ], + "287": [ + { + "x": 16, + "y": 44 + } + ], + "294": [ + { + "x": 17, + "y": 44 + } + ], + "340": [ + { + "x": 18, + "y": 44 + } + ], + "349": [ + { + "x": 19, + "y": 44 + } + ], + "395": [ + { + "x": 20, + "y": 44 + } + ], + "404": [ + { + "x": 21, + "y": 44 + } + ], + "450": [ + { + "x": 22, + "y": 44 + } + ], + "458": [ + { + "x": 23, + "y": 44 + } + ], + "484": [ + { + "x": 24, + "y": 44 + } + ], + "493": [ + { + "x": 25, + "y": 44 + } + ], + "538": [ + { + "x": 26, + "y": 44 + } + ], + "548": [ + { + "x": 27, + "y": 44 + } + ], + "593": [ + { + "x": 28, + "y": 44 + } + ], + "603": [ + { + "x": 29, + "y": 44 + } + ], + "648": [ + { + "x": 30, + "y": 44 + } + ], + "655": [ + { + "x": 31, + "y": 44 + } + ], + "680": [ + { + "x": 32, + "y": 44 + } + ], + "689": [ + { + "x": 33, + "y": 44 + } + ], + "735": [ + { + "x": 34, + "y": 44 + } + ], + "744": [ + { + "x": 35, + "y": 44 + } + ], + "790": [ + { + "x": 36, + "y": 44 + } + ], + "799": [ + { + "x": 37, + "y": 44 + } + ], + "845": [ + { + "x": 38, + "y": 44 + } + ], + "853": [ + { + "x": 39, + "y": 44 + } + ], + "879": [ + { + "x": 40, + "y": 44 + } + ], + "888": [ + { + "x": 41, + "y": 44 + } + ], + "933": [ + { + "x": 42, + "y": 44 + } + ], + "943": [ + { + "x": 43, + "y": 44 + } + ], + "988": [ + { + "x": 44, + "y": 44 + } + ], + "998": [ + { + "x": 45, + "y": 44 + } + ], + "1043": [ + { + "x": 46, + "y": 44 + } + ], + "1050": [ + { + "x": 47, + "y": 44 + } + ], + "1075": [ + { + "x": 48, + "y": 44 + } + ], + "1084": [ + { + "x": 49, + "y": 44 + } + ], + "1130": [ + { + "x": 50, + "y": 44 + } + ], + "1139": [ + { + "x": 51, + "y": 44 + } + ], + "1185": [ + { + "x": 52, + "y": 44 + } + ], + "1194": [ + { + "x": 53, + "y": 44 + } + ], + "1240": [ + { + "x": 54, + "y": 44 + } + ], + "1248": [ + { + "x": 55, + "y": 44 + } + ], + "1274": [ + { + "x": 56, + "y": 44 + } + ], + "1283": [ + { + "x": 57, + "y": 44 + } + ], + "1328": [ + { + "x": 58, + "y": 44 + } + ], + "1338": [ + { + "x": 59, + "y": 44 + } + ], + "1383": [ + { + "x": 60, + "y": 44 + } + ], + "1393": [ + { + "x": 61, + "y": 44 + } + ], + "1438": [ + { + "x": 62, + "y": 44 + } + ], + "1443": [ + { + "x": 63, + "y": 44 + } + ], + "1460": [ + { + "x": 64, + "y": 44 + } + ], + "1467": [ + { + "x": 65, + "y": 44 + } + ], + "1501": [ + { + "x": 66, + "y": 44 + } + ], + "1508": [ + { + "x": 67, + "y": 44 + } + ], + "1542": [ + { + "x": 68, + "y": 44 + } + ], + "1549": [ + { + "x": 69, + "y": 44 + } + ], + "1583": [ + { + "x": 70, + "y": 44 + } + ], + "1589": [ + { + "x": 71, + "y": 44 + } + ], + "1607": [ + { + "x": 72, + "y": 44 + } + ], + "1614": [ + { + "x": 73, + "y": 44 + } + ], + "1647": [ + { + "x": 74, + "y": 44 + } + ], + "1655": [ + { + "x": 75, + "y": 44 + } + ], + "1688": [ + { + "x": 76, + "y": 44 + } + ], + "1694": [ + { + "x": 77, + "y": 44 + } + ], + "2": [ + { + "x": 1, + "y": 45 + } + ], + "43": [ + { + "x": 3, + "y": 45 + } + ], + "84": [ + { + "x": 5, + "y": 45 + } + ], + "149": [ + { + "x": 9, + "y": 45 + } + ], + "190": [ + { + "x": 11, + "y": 45 + } + ], + "231": [ + { + "x": 13, + "y": 45 + } + ], + "293": [ + { + "x": 17, + "y": 45 + } + ], + "348": [ + { + "x": 19, + "y": 45 + } + ], + "403": [ + { + "x": 21, + "y": 45 + } + ], + "492": [ + { + "x": 25, + "y": 45 + } + ], + "547": [ + { + "x": 27, + "y": 45 + } + ], + "602": [ + { + "x": 29, + "y": 45 + } + ], + "688": [ + { + "x": 33, + "y": 45 + } + ], + "743": [ + { + "x": 35, + "y": 45 + } + ], + "798": [ + { + "x": 37, + "y": 45 + } + ], + "887": [ + { + "x": 41, + "y": 45 + } + ], + "942": [ + { + "x": 43, + "y": 45 + } + ], + "997": [ + { + "x": 45, + "y": 45 + } + ], + "1083": [ + { + "x": 49, + "y": 45 + } + ], + "1138": [ + { + "x": 51, + "y": 45 + } + ], + "1193": [ + { + "x": 53, + "y": 45 + } + ], + "1282": [ + { + "x": 57, + "y": 45 + } + ], + "1337": [ + { + "x": 59, + "y": 45 + } + ], + "1392": [ + { + "x": 61, + "y": 45 + } + ], + "1466": [ + { + "x": 65, + "y": 45 + } + ], + "1507": [ + { + "x": 67, + "y": 45 + } + ], + "1548": [ + { + "x": 69, + "y": 45 + } + ], + "1613": [ + { + "x": 73, + "y": 45 + } + ], + "1654": [ + { + "x": 75, + "y": 45 + } + ], + "1693": [ + { + "x": 77, + "y": 45 + } + ], + "189": [ + { + "x": 11, + "y": 46 + } + ], + "230": [ + { + "x": 13, + "y": 46 + } + ], + "546": [ + { + "x": 27, + "y": 46 + } + ], + "601": [ + { + "x": 29, + "y": 46 + } + ], + "941": [ + { + "x": 43, + "y": 46 + } + ], + "996": [ + { + "x": 45, + "y": 46 + } + ], + "1336": [ + { + "x": 59, + "y": 46 + } + ], + "1391": [ + { + "x": 61, + "y": 46 + } + ], + "1653": [ + { + "x": 75, + "y": 46 + } + ], + "1692": [ + { + "x": 77, + "y": 46 + } + ], + "1": [ + { + "x": 1, + "y": 47 + } + ], + "36": [ + { + "x": 2, + "y": 47 + } + ], + "42": [ + { + "x": 3, + "y": 47 + } + ], + "77": [ + { + "x": 4, + "y": 47 + } + ], + "83": [ + { + "x": 5, + "y": 47 + } + ], + "118": [ + { + "x": 6, + "y": 47 + } + ], + "124": [ + { + "x": 7, + "y": 47 + } + ], + "142": [ + { + "x": 8, + "y": 47 + } + ], + "148": [ + { + "x": 9, + "y": 47 + } + ], + "292": [ + { + "x": 17, + "y": 47 + } + ], + "339": [ + { + "x": 18, + "y": 47 + } + ], + "347": [ + { + "x": 19, + "y": 47 + } + ], + "394": [ + { + "x": 20, + "y": 47 + } + ], + "402": [ + { + "x": 21, + "y": 47 + } + ], + "449": [ + { + "x": 22, + "y": 47 + } + ], + "457": [ + { + "x": 23, + "y": 47 + } + ], + "483": [ + { + "x": 24, + "y": 47 + } + ], + "491": [ + { + "x": 25, + "y": 47 + } + ], + "687": [ + { + "x": 33, + "y": 47 + } + ], + "734": [ + { + "x": 34, + "y": 47 + } + ], + "742": [ + { + "x": 35, + "y": 47 + } + ], + "789": [ + { + "x": 36, + "y": 47 + } + ], + "797": [ + { + "x": 37, + "y": 47 + } + ], + "844": [ + { + "x": 38, + "y": 47 + } + ], + "852": [ + { + "x": 39, + "y": 47 + } + ], + "878": [ + { + "x": 40, + "y": 47 + } + ], + "886": [ + { + "x": 41, + "y": 47 + } + ], + "1082": [ + { + "x": 49, + "y": 47 + } + ], + "1129": [ + { + "x": 50, + "y": 47 + } + ], + "1137": [ + { + "x": 51, + "y": 47 + } + ], + "1184": [ + { + "x": 52, + "y": 47 + } + ], + "1192": [ + { + "x": 53, + "y": 47 + } + ], + "1239": [ + { + "x": 54, + "y": 47 + } + ], + "1247": [ + { + "x": 55, + "y": 47 + } + ], + "1273": [ + { + "x": 56, + "y": 47 + } + ], + "1281": [ + { + "x": 57, + "y": 47 + } + ], + "1465": [ + { + "x": 65, + "y": 47 + } + ], + "1500": [ + { + "x": 66, + "y": 47 + } + ], + "1506": [ + { + "x": 67, + "y": 47 + } + ], + "1541": [ + { + "x": 68, + "y": 47 + } + ], + "1547": [ + { + "x": 69, + "y": 47 + } + ], + "1582": [ + { + "x": 70, + "y": 47 + } + ], + "1588": [ + { + "x": 71, + "y": 47 + } + ], + "1606": [ + { + "x": 72, + "y": 47 + } + ], + "1612": [ + { + "x": 73, + "y": 47 + } + ] + }, + "coordinate_to_path_id": { + "17,0": 338, + "18,0": 346, + "19,0": 393, + "20,0": 401, + "21,0": 448, + "22,0": 456, + "23,0": 482, + "24,0": 490, + "25,0": 537, + "26,0": 544, + "27,0": 592, + "28,0": 599, + "29,0": 647, + "30,0": 654, + "31,0": 679, + "32,0": 686, + "33,0": 733, + "34,0": 741, + "35,0": 788, + "36,0": 796, + "37,0": 843, + "38,0": 851, + "39,0": 877, + "40,0": 885, + "41,0": 932, + "42,0": 939, + "43,0": 987, + "44,0": 994, + "45,0": 1042, + "46,0": 1049, + "47,0": 1074, + "48,0": 1081, + "49,0": 1128, + "50,0": 1136, + "51,0": 1183, + "52,0": 1191, + "53,0": 1238, + "54,0": 1246, + "55,0": 1272, + "56,0": 1280, + "57,0": 1327, + "58,0": 1334, + "59,0": 1382, + "60,0": 1389, + "61,0": 1437, + "17,1": 337, + "19,1": 392, + "21,1": 447, + "25,1": 536, + "27,1": 591, + "29,1": 646, + "33,1": 732, + "35,1": 787, + "37,1": 842, + "41,1": 931, + "43,1": 986, + "45,1": 1041, + "49,1": 1127, + "51,1": 1182, + "53,1": 1237, + "57,1": 1326, + "59,1": 1381, + "61,1": 1436, + "17,2": 336, + "19,2": 391, + "21,2": 446, + "23,2": 481, + "25,2": 535, + "27,2": 590, + "29,2": 645, + "31,2": 678, + "33,2": 731, + "35,2": 786, + "37,2": 841, + "39,2": 876, + "41,2": 930, + "43,2": 985, + "45,2": 1040, + "47,2": 1073, + "49,2": 1126, + "51,2": 1181, + "53,2": 1236, + "55,2": 1271, + "57,2": 1325, + "59,2": 1380, + "61,2": 1435, + "17,3": 335, + "19,3": 390, + "21,3": 445, + "23,3": 480, + "25,3": 534, + "27,3": 589, + "29,3": 644, + "31,3": 677, + "33,3": 730, + "35,3": 785, + "37,3": 840, + "39,3": 875, + "41,3": 929, + "43,3": 984, + "45,3": 1039, + "47,3": 1072, + "49,3": 1125, + "51,3": 1180, + "53,3": 1235, + "55,3": 1270, + "57,3": 1324, + "59,3": 1379, + "61,3": 1434, + "17,4": 334, + "19,4": 389, + "21,4": 444, + "23,4": 479, + "25,4": 533, + "27,4": 588, + "29,4": 643, + "31,4": 676, + "33,4": 729, + "35,4": 784, + "37,4": 839, + "39,4": 874, + "41,4": 928, + "43,4": 983, + "45,4": 1038, + "47,4": 1071, + "49,4": 1124, + "51,4": 1179, + "53,4": 1234, + "55,4": 1269, + "57,4": 1323, + "59,4": 1378, + "61,4": 1433, + "17,5": 333, + "19,5": 388, + "21,5": 443, + "25,5": 532, + "27,5": 587, + "29,5": 642, + "33,5": 728, + "35,5": 783, + "37,5": 838, + "41,5": 927, + "43,5": 982, + "45,5": 1037, + "49,5": 1123, + "51,5": 1178, + "53,5": 1233, + "57,5": 1322, + "59,5": 1377, + "61,5": 1432, + "17,6": 332, + "18,6": 345, + "19,6": 387, + "20,6": 400, + "21,6": 442, + "22,6": 455, + "23,6": 478, + "24,6": 489, + "25,6": 531, + "26,6": 543, + "27,6": 586, + "28,6": 598, + "29,6": 641, + "30,6": 653, + "31,6": 675, + "32,6": 685, + "33,6": 727, + "34,6": 740, + "35,6": 782, + "36,6": 795, + "37,6": 837, + "38,6": 850, + "39,6": 873, + "40,6": 884, + "41,6": 926, + "42,6": 938, + "43,6": 981, + "44,6": 993, + "45,6": 1036, + "46,6": 1048, + "47,6": 1070, + "48,6": 1080, + "49,6": 1122, + "50,6": 1135, + "51,6": 1177, + "52,6": 1190, + "53,6": 1232, + "54,6": 1245, + "55,6": 1268, + "56,6": 1279, + "57,6": 1321, + "58,6": 1333, + "59,6": 1376, + "60,6": 1388, + "61,6": 1431, + "17,7": 331, + "19,7": 386, + "21,7": 441, + "25,7": 530, + "27,7": 585, + "29,7": 640, + "33,7": 726, + "35,7": 781, + "37,7": 836, + "41,7": 925, + "43,7": 980, + "45,7": 1035, + "49,7": 1121, + "51,7": 1176, + "53,7": 1231, + "57,7": 1320, + "59,7": 1375, + "61,7": 1430, + "17,8": 330, + "19,8": 385, + "21,8": 440, + "23,8": 477, + "25,8": 529, + "27,8": 584, + "29,8": 639, + "31,8": 674, + "33,8": 725, + "35,8": 780, + "37,8": 835, + "39,8": 872, + "41,8": 924, + "43,8": 979, + "45,8": 1034, + "47,8": 1069, + "49,8": 1120, + "51,8": 1175, + "53,8": 1230, + "55,8": 1267, + "57,8": 1319, + "59,8": 1374, + "61,8": 1429, + "17,9": 329, + "19,9": 384, + "21,9": 439, + "23,9": 476, + "25,9": 528, + "27,9": 583, + "29,9": 638, + "31,9": 673, + "33,9": 724, + "35,9": 779, + "37,9": 834, + "39,9": 871, + "41,9": 923, + "43,9": 978, + "45,9": 1033, + "47,9": 1068, + "49,9": 1119, + "51,9": 1174, + "53,9": 1229, + "55,9": 1266, + "57,9": 1318, + "59,9": 1373, + "61,9": 1428, + "17,10": 328, + "19,10": 383, + "21,10": 438, + "23,10": 475, + "25,10": 527, + "27,10": 582, + "29,10": 637, + "31,10": 672, + "33,10": 723, + "35,10": 778, + "37,10": 833, + "39,10": 870, + "41,10": 922, + "43,10": 977, + "45,10": 1032, + "47,10": 1067, + "49,10": 1118, + "51,10": 1173, + "53,10": 1228, + "55,10": 1265, + "57,10": 1317, + "59,10": 1372, + "61,10": 1427, + "17,11": 327, + "19,11": 382, + "21,11": 437, + "25,11": 526, + "27,11": 581, + "29,11": 636, + "33,11": 722, + "35,11": 777, + "37,11": 832, + "41,11": 921, + "43,11": 976, + "45,11": 1031, + "49,11": 1117, + "51,11": 1172, + "53,11": 1227, + "57,11": 1316, + "59,11": 1371, + "61,11": 1426, + "1,12": 35, + "2,12": 41, + "3,12": 76, + "4,12": 82, + "5,12": 117, + "6,12": 123, + "7,12": 141, + "8,12": 147, + "9,12": 182, + "10,12": 187, + "11,12": 223, + "12,12": 228, + "13,12": 264, + "14,12": 269, + "15,12": 286, + "16,12": 291, + "17,12": 326, + "18,12": 344, + "19,12": 381, + "20,12": 399, + "21,12": 436, + "22,12": 454, + "23,12": 474, + "24,12": 488, + "25,12": 525, + "26,12": 542, + "27,12": 580, + "28,12": 597, + "29,12": 635, + "30,12": 652, + "31,12": 671, + "32,12": 684, + "33,12": 721, + "34,12": 739, + "35,12": 776, + "36,12": 794, + "37,12": 831, + "38,12": 849, + "39,12": 869, + "40,12": 883, + "41,12": 920, + "42,12": 937, + "43,12": 975, + "44,12": 992, + "45,12": 1030, + "46,12": 1047, + "47,12": 1066, + "48,12": 1079, + "49,12": 1116, + "50,12": 1134, + "51,12": 1171, + "52,12": 1189, + "53,12": 1226, + "54,12": 1244, + "55,12": 1264, + "56,12": 1278, + "57,12": 1315, + "58,12": 1332, + "59,12": 1370, + "60,12": 1387, + "61,12": 1425, + "62,12": 1442, + "63,12": 1459, + "64,12": 1464, + "65,12": 1499, + "66,12": 1505, + "67,12": 1540, + "68,12": 1546, + "69,12": 1581, + "70,12": 1587, + "71,12": 1605, + "72,12": 1611, + "73,12": 1646, + "74,12": 1651, + "75,12": 1687, + "1,13": 34, + "3,13": 75, + "5,13": 116, + "9,13": 181, + "11,13": 222, + "13,13": 263, + "17,13": 325, + "19,13": 380, + "21,13": 435, + "25,13": 524, + "27,13": 579, + "29,13": 634, + "33,13": 720, + "35,13": 775, + "37,13": 830, + "41,13": 919, + "43,13": 974, + "45,13": 1029, + "49,13": 1115, + "51,13": 1170, + "53,13": 1225, + "57,13": 1314, + "59,13": 1369, + "61,13": 1424, + "65,13": 1498, + "67,13": 1539, + "69,13": 1580, + "73,13": 1645, + "75,13": 1686, + "1,14": 33, + "3,14": 74, + "5,14": 115, + "7,14": 140, + "9,14": 180, + "11,14": 221, + "13,14": 262, + "15,14": 285, + "17,14": 324, + "19,14": 379, + "21,14": 434, + "23,14": 473, + "25,14": 523, + "27,14": 578, + "29,14": 633, + "31,14": 670, + "33,14": 719, + "35,14": 774, + "37,14": 829, + "39,14": 868, + "41,14": 918, + "43,14": 973, + "45,14": 1028, + "47,14": 1065, + "49,14": 1114, + "51,14": 1169, + "53,14": 1224, + "55,14": 1263, + "57,14": 1313, + "59,14": 1368, + "61,14": 1423, + "63,14": 1458, + "65,14": 1497, + "67,14": 1538, + "69,14": 1579, + "71,14": 1604, + "73,14": 1644, + "75,14": 1685, + "1,15": 32, + "3,15": 73, + "5,15": 114, + "9,15": 179, + "11,15": 220, + "13,15": 261, + "17,15": 323, + "19,15": 378, + "21,15": 433, + "25,15": 522, + "27,15": 577, + "29,15": 632, + "33,15": 718, + "35,15": 773, + "37,15": 828, + "41,15": 917, + "43,15": 972, + "45,15": 1027, + "49,15": 1113, + "51,15": 1168, + "53,15": 1223, + "57,15": 1312, + "59,15": 1367, + "61,15": 1422, + "65,15": 1496, + "67,15": 1537, + "69,15": 1578, + "73,15": 1643, + "75,15": 1684, + "1,16": 31, + "3,16": 72, + "5,16": 113, + "7,16": 139, + "9,16": 178, + "11,16": 219, + "13,16": 260, + "15,16": 284, + "17,16": 322, + "19,16": 377, + "21,16": 432, + "23,16": 472, + "25,16": 521, + "27,16": 576, + "29,16": 631, + "31,16": 669, + "33,16": 717, + "35,16": 772, + "37,16": 827, + "39,16": 867, + "41,16": 916, + "43,16": 971, + "45,16": 1026, + "47,16": 1064, + "49,16": 1112, + "51,16": 1167, + "53,16": 1222, + "55,16": 1262, + "57,16": 1311, + "59,16": 1366, + "61,16": 1421, + "63,16": 1457, + "65,16": 1495, + "67,16": 1536, + "69,16": 1577, + "71,16": 1603, + "73,16": 1642, + "75,16": 1683, + "1,17": 30, + "3,17": 71, + "5,17": 112, + "7,17": 138, + "9,17": 177, + "11,17": 218, + "13,17": 259, + "15,17": 283, + "17,17": 321, + "19,17": 376, + "21,17": 431, + "23,17": 471, + "25,17": 520, + "27,17": 575, + "29,17": 630, + "31,17": 668, + "33,17": 716, + "35,17": 771, + "37,17": 826, + "39,17": 866, + "41,17": 915, + "43,17": 970, + "45,17": 1025, + "47,17": 1063, + "49,17": 1111, + "51,17": 1166, + "53,17": 1221, + "55,17": 1261, + "57,17": 1310, + "59,17": 1365, + "61,17": 1420, + "63,17": 1456, + "65,17": 1494, + "67,17": 1535, + "69,17": 1576, + "71,17": 1602, + "73,17": 1641, + "75,17": 1682, + "1,18": 29, + "3,18": 70, + "5,18": 111, + "7,18": 137, + "9,18": 176, + "11,18": 217, + "13,18": 258, + "15,18": 282, + "17,18": 320, + "19,18": 375, + "21,18": 430, + "23,18": 470, + "25,18": 519, + "27,18": 574, + "29,18": 629, + "31,18": 667, + "33,18": 715, + "35,18": 770, + "37,18": 825, + "39,18": 865, + "41,18": 914, + "43,18": 969, + "45,18": 1024, + "47,18": 1062, + "49,18": 1110, + "51,18": 1165, + "53,18": 1220, + "55,18": 1260, + "57,18": 1309, + "59,18": 1364, + "61,18": 1419, + "63,18": 1455, + "65,18": 1493, + "67,18": 1534, + "69,18": 1575, + "71,18": 1601, + "73,18": 1640, + "75,18": 1681, + "1,19": 28, + "3,19": 69, + "5,19": 110, + "9,19": 175, + "11,19": 216, + "13,19": 257, + "17,19": 319, + "19,19": 374, + "21,19": 429, + "25,19": 518, + "27,19": 573, + "29,19": 628, + "33,19": 714, + "35,19": 769, + "37,19": 824, + "41,19": 913, + "43,19": 968, + "45,19": 1023, + "49,19": 1109, + "51,19": 1164, + "53,19": 1219, + "57,19": 1308, + "59,19": 1363, + "61,19": 1418, + "65,19": 1492, + "67,19": 1533, + "69,19": 1574, + "73,19": 1639, + "75,19": 1680, + "1,20": 27, + "3,20": 68, + "5,20": 109, + "7,20": 136, + "9,20": 174, + "11,20": 215, + "13,20": 256, + "15,20": 281, + "17,20": 318, + "19,20": 373, + "21,20": 428, + "23,20": 469, + "25,20": 517, + "27,20": 572, + "29,20": 627, + "31,20": 666, + "33,20": 713, + "35,20": 768, + "37,20": 823, + "39,20": 864, + "41,20": 912, + "43,20": 967, + "45,20": 1022, + "47,20": 1061, + "49,20": 1108, + "51,20": 1163, + "53,20": 1218, + "55,20": 1259, + "57,20": 1307, + "59,20": 1362, + "61,20": 1417, + "63,20": 1454, + "65,20": 1491, + "67,20": 1532, + "69,20": 1573, + "71,20": 1600, + "73,20": 1638, + "75,20": 1679, + "1,21": 26, + "3,21": 67, + "5,21": 108, + "9,21": 173, + "11,21": 214, + "13,21": 255, + "17,21": 317, + "19,21": 372, + "21,21": 427, + "25,21": 516, + "27,21": 571, + "29,21": 626, + "33,21": 712, + "35,21": 767, + "37,21": 822, + "41,21": 911, + "43,21": 966, + "45,21": 1021, + "49,21": 1107, + "51,21": 1162, + "53,21": 1217, + "57,21": 1306, + "59,21": 1361, + "61,21": 1416, + "65,21": 1490, + "67,21": 1531, + "69,21": 1572, + "73,21": 1637, + "75,21": 1678, + "1,22": 25, + "2,22": 40, + "3,22": 66, + "4,22": 81, + "5,22": 107, + "6,22": 122, + "7,22": 135, + "8,22": 146, + "9,22": 172, + "10,22": 186, + "11,22": 213, + "12,22": 227, + "13,22": 254, + "14,22": 268, + "15,22": 280, + "16,22": 290, + "17,22": 316, + "18,22": 343, + "19,22": 371, + "20,22": 398, + "21,22": 426, + "22,22": 453, + "23,22": 468, + "24,22": 487, + "25,22": 515, + "26,22": 541, + "27,22": 570, + "28,22": 596, + "29,22": 625, + "30,22": 651, + "31,22": 665, + "32,22": 683, + "33,22": 711, + "34,22": 738, + "35,22": 766, + "36,22": 793, + "37,22": 821, + "38,22": 848, + "39,22": 863, + "40,22": 882, + "41,22": 910, + "42,22": 936, + "43,22": 965, + "44,22": 991, + "45,22": 1020, + "46,22": 1046, + "47,22": 1060, + "48,22": 1078, + "49,22": 1106, + "50,22": 1133, + "51,22": 1161, + "52,22": 1188, + "53,22": 1216, + "54,22": 1243, + "55,22": 1258, + "56,22": 1277, + "57,22": 1305, + "58,22": 1331, + "59,22": 1360, + "60,22": 1386, + "61,22": 1415, + "62,22": 1441, + "63,22": 1453, + "64,22": 1463, + "65,22": 1489, + "66,22": 1504, + "67,22": 1530, + "68,22": 1545, + "69,22": 1571, + "70,22": 1586, + "71,22": 1599, + "72,22": 1610, + "73,22": 1636, + "74,22": 1650, + "75,22": 1677, + "1,23": 24, + "3,23": 65, + "5,23": 106, + "9,23": 171, + "11,23": 212, + "13,23": 253, + "17,23": 315, + "19,23": 370, + "21,23": 425, + "25,23": 514, + "27,23": 569, + "29,23": 624, + "33,23": 710, + "35,23": 765, + "37,23": 820, + "41,23": 909, + "43,23": 964, + "45,23": 1019, + "49,23": 1105, + "51,23": 1160, + "53,23": 1215, + "57,23": 1304, + "59,23": 1359, + "61,23": 1414, + "65,23": 1488, + "67,23": 1529, + "69,23": 1570, + "73,23": 1635, + "75,23": 1676, + "1,24": 23, + "3,24": 64, + "5,24": 105, + "9,24": 170, + "11,24": 211, + "13,24": 252, + "17,24": 314, + "19,24": 369, + "21,24": 424, + "25,24": 513, + "27,24": 568, + "29,24": 623, + "33,24": 709, + "35,24": 764, + "37,24": 819, + "41,24": 908, + "43,24": 963, + "45,24": 1018, + "49,24": 1104, + "51,24": 1159, + "53,24": 1214, + "57,24": 1303, + "59,24": 1358, + "61,24": 1413, + "65,24": 1487, + "67,24": 1528, + "69,24": 1569, + "73,24": 1634, + "75,24": 1675, + "1,25": 22, + "3,25": 63, + "5,25": 104, + "9,25": 169, + "11,25": 210, + "13,25": 251, + "17,25": 313, + "19,25": 368, + "21,25": 423, + "25,25": 512, + "27,25": 567, + "29,25": 622, + "33,25": 708, + "35,25": 763, + "37,25": 818, + "41,25": 907, + "43,25": 962, + "45,25": 1017, + "49,25": 1103, + "51,25": 1158, + "53,25": 1213, + "57,25": 1302, + "59,25": 1357, + "61,25": 1412, + "65,25": 1486, + "67,25": 1527, + "69,25": 1568, + "73,25": 1633, + "75,25": 1674, + "1,26": 21, + "3,26": 62, + "5,26": 103, + "9,26": 168, + "11,26": 209, + "13,26": 250, + "17,26": 312, + "19,26": 367, + "21,26": 422, + "25,26": 511, + "27,26": 566, + "29,26": 621, + "33,26": 707, + "35,26": 762, + "37,26": 817, + "41,26": 906, + "43,26": 961, + "45,26": 1016, + "49,26": 1102, + "51,26": 1157, + "53,26": 1212, + "57,26": 1301, + "59,26": 1356, + "61,26": 1411, + "65,26": 1485, + "67,26": 1526, + "69,26": 1567, + "73,26": 1632, + "75,26": 1673, + "1,27": 20, + "3,27": 61, + "5,27": 102, + "9,27": 167, + "11,27": 208, + "13,27": 249, + "17,27": 311, + "19,27": 366, + "21,27": 421, + "25,27": 510, + "27,27": 565, + "29,27": 620, + "33,27": 706, + "35,27": 761, + "37,27": 816, + "41,27": 905, + "43,27": 960, + "45,27": 1015, + "49,27": 1101, + "51,27": 1156, + "53,27": 1211, + "57,27": 1300, + "59,27": 1355, + "61,27": 1410, + "65,27": 1484, + "67,27": 1525, + "69,27": 1566, + "73,27": 1631, + "75,27": 1672, + "1,28": 19, + "3,28": 60, + "5,28": 101, + "7,28": 134, + "9,28": 166, + "11,28": 207, + "13,28": 248, + "15,28": 279, + "17,28": 310, + "19,28": 365, + "21,28": 420, + "23,28": 467, + "25,28": 509, + "27,28": 564, + "29,28": 619, + "31,28": 664, + "33,28": 705, + "35,28": 760, + "37,28": 815, + "39,28": 862, + "41,28": 904, + "43,28": 959, + "45,28": 1014, + "47,28": 1059, + "49,28": 1100, + "51,28": 1155, + "53,28": 1210, + "55,28": 1257, + "57,28": 1299, + "59,28": 1354, + "61,28": 1409, + "63,28": 1452, + "65,28": 1483, + "67,28": 1524, + "69,28": 1565, + "71,28": 1598, + "73,28": 1630, + "75,28": 1671, + "1,29": 18, + "3,29": 59, + "5,29": 100, + "9,29": 165, + "11,29": 206, + "13,29": 247, + "17,29": 309, + "19,29": 364, + "21,29": 419, + "25,29": 508, + "27,29": 563, + "29,29": 618, + "33,29": 704, + "35,29": 759, + "37,29": 814, + "41,29": 903, + "43,29": 958, + "45,29": 1013, + "49,29": 1099, + "51,29": 1154, + "53,29": 1209, + "57,29": 1298, + "59,29": 1353, + "61,29": 1408, + "65,29": 1482, + "67,29": 1523, + "69,29": 1564, + "73,29": 1629, + "75,29": 1670, + "1,30": 17, + "3,30": 58, + "5,30": 99, + "7,30": 133, + "9,30": 164, + "11,30": 205, + "13,30": 246, + "15,30": 278, + "17,30": 308, + "19,30": 363, + "21,30": 418, + "23,30": 466, + "25,30": 507, + "27,30": 562, + "29,30": 617, + "31,30": 663, + "33,30": 703, + "35,30": 758, + "37,30": 813, + "39,30": 861, + "41,30": 902, + "43,30": 957, + "45,30": 1012, + "47,30": 1058, + "49,30": 1098, + "51,30": 1153, + "53,30": 1208, + "55,30": 1256, + "57,30": 1297, + "59,30": 1352, + "61,30": 1407, + "63,30": 1451, + "65,30": 1481, + "67,30": 1522, + "69,30": 1563, + "71,30": 1597, + "73,30": 1628, + "75,30": 1669, + "1,31": 16, + "3,31": 57, + "5,31": 98, + "9,31": 163, + "11,31": 204, + "13,31": 245, + "17,31": 307, + "19,31": 362, + "21,31": 417, + "25,31": 506, + "27,31": 561, + "29,31": 616, + "33,31": 702, + "35,31": 757, + "37,31": 812, + "41,31": 901, + "43,31": 956, + "45,31": 1011, + "49,31": 1097, + "51,31": 1152, + "53,31": 1207, + "57,31": 1296, + "59,31": 1351, + "61,31": 1406, + "65,31": 1480, + "67,31": 1521, + "69,31": 1562, + "73,31": 1627, + "75,31": 1668, + "1,32": 15, + "2,32": 39, + "3,32": 56, + "4,32": 80, + "5,32": 97, + "6,32": 121, + "7,32": 132, + "8,32": 145, + "9,32": 162, + "10,32": 185, + "11,32": 203, + "12,32": 226, + "13,32": 244, + "14,32": 267, + "15,32": 277, + "16,32": 289, + "17,32": 306, + "18,32": 342, + "19,32": 361, + "20,32": 397, + "21,32": 416, + "22,32": 452, + "23,32": 465, + "24,32": 486, + "25,32": 505, + "26,32": 540, + "27,32": 560, + "28,32": 595, + "29,32": 615, + "30,32": 650, + "31,32": 662, + "32,32": 682, + "33,32": 701, + "34,32": 737, + "35,32": 756, + "36,32": 792, + "37,32": 811, + "38,32": 847, + "39,32": 860, + "40,32": 881, + "41,32": 900, + "42,32": 935, + "43,32": 955, + "44,32": 990, + "45,32": 1010, + "46,32": 1045, + "47,32": 1057, + "48,32": 1077, + "49,32": 1096, + "50,32": 1132, + "51,32": 1151, + "52,32": 1187, + "53,32": 1206, + "54,32": 1242, + "55,32": 1255, + "56,32": 1276, + "57,32": 1295, + "58,32": 1330, + "59,32": 1350, + "60,32": 1385, + "61,32": 1405, + "62,32": 1440, + "63,32": 1450, + "64,32": 1462, + "65,32": 1479, + "66,32": 1503, + "67,32": 1520, + "68,32": 1544, + "69,32": 1561, + "70,32": 1585, + "71,32": 1596, + "72,32": 1609, + "73,32": 1626, + "74,32": 1649, + "75,32": 1667, + "76,32": 1690, + "77,32": 1706, + "1,33": 14, + "3,33": 55, + "5,33": 96, + "9,33": 161, + "11,33": 202, + "13,33": 243, + "17,33": 305, + "19,33": 360, + "21,33": 415, + "25,33": 504, + "27,33": 559, + "29,33": 614, + "33,33": 700, + "35,33": 755, + "37,33": 810, + "41,33": 899, + "43,33": 954, + "45,33": 1009, + "49,33": 1095, + "51,33": 1150, + "53,33": 1205, + "57,33": 1294, + "59,33": 1349, + "61,33": 1404, + "65,33": 1478, + "67,33": 1519, + "69,33": 1560, + "73,33": 1625, + "75,33": 1666, + "77,33": 1705, + "1,34": 13, + "3,34": 54, + "5,34": 95, + "7,34": 131, + "9,34": 160, + "11,34": 201, + "13,34": 242, + "15,34": 276, + "17,34": 304, + "19,34": 359, + "21,34": 414, + "23,34": 464, + "25,34": 503, + "27,34": 558, + "29,34": 613, + "31,34": 661, + "33,34": 699, + "35,34": 754, + "37,34": 809, + "39,34": 859, + "41,34": 898, + "43,34": 953, + "45,34": 1008, + "47,34": 1056, + "49,34": 1094, + "51,34": 1149, + "53,34": 1204, + "55,34": 1254, + "57,34": 1293, + "59,34": 1348, + "61,34": 1403, + "63,34": 1449, + "65,34": 1477, + "67,34": 1518, + "69,34": 1559, + "71,34": 1595, + "73,34": 1624, + "75,34": 1665, + "77,34": 1704, + "1,35": 12, + "3,35": 53, + "5,35": 94, + "9,35": 159, + "11,35": 200, + "13,35": 241, + "17,35": 303, + "19,35": 358, + "21,35": 413, + "25,35": 502, + "27,35": 557, + "29,35": 612, + "33,35": 698, + "35,35": 753, + "37,35": 808, + "41,35": 897, + "43,35": 952, + "45,35": 1007, + "49,35": 1093, + "51,35": 1148, + "53,35": 1203, + "57,35": 1292, + "59,35": 1347, + "61,35": 1402, + "65,35": 1476, + "67,35": 1517, + "69,35": 1558, + "73,35": 1623, + "75,35": 1664, + "77,35": 1703, + "1,36": 11, + "3,36": 52, + "5,36": 93, + "7,36": 130, + "9,36": 158, + "11,36": 199, + "13,36": 240, + "15,36": 275, + "17,36": 302, + "19,36": 357, + "21,36": 412, + "23,36": 463, + "25,36": 501, + "27,36": 556, + "29,36": 611, + "31,36": 660, + "33,36": 697, + "35,36": 752, + "37,36": 807, + "39,36": 858, + "41,36": 896, + "43,36": 951, + "45,36": 1006, + "47,36": 1055, + "49,36": 1092, + "51,36": 1147, + "53,36": 1202, + "55,36": 1253, + "57,36": 1291, + "59,36": 1346, + "61,36": 1401, + "63,36": 1448, + "65,36": 1475, + "67,36": 1516, + "69,36": 1557, + "71,36": 1594, + "73,36": 1622, + "75,36": 1663, + "77,36": 1702, + "1,37": 10, + "3,37": 51, + "5,37": 92, + "7,37": 129, + "9,37": 157, + "11,37": 198, + "13,37": 239, + "15,37": 274, + "17,37": 301, + "19,37": 356, + "21,37": 411, + "23,37": 462, + "25,37": 500, + "27,37": 555, + "29,37": 610, + "31,37": 659, + "33,37": 696, + "35,37": 751, + "37,37": 806, + "39,37": 857, + "41,37": 895, + "43,37": 950, + "45,37": 1005, + "47,37": 1054, + "49,37": 1091, + "51,37": 1146, + "53,37": 1201, + "55,37": 1252, + "57,37": 1290, + "59,37": 1345, + "61,37": 1400, + "63,37": 1447, + "65,37": 1474, + "67,37": 1515, + "69,37": 1556, + "71,37": 1593, + "73,37": 1621, + "75,37": 1662, + "77,37": 1701, + "1,38": 9, + "3,38": 50, + "5,38": 91, + "7,38": 128, + "9,38": 156, + "11,38": 197, + "13,38": 238, + "15,38": 273, + "17,38": 300, + "19,38": 355, + "21,38": 410, + "23,38": 461, + "25,38": 499, + "27,38": 554, + "29,38": 609, + "31,38": 658, + "33,38": 695, + "35,38": 750, + "37,38": 805, + "39,38": 856, + "41,38": 894, + "43,38": 949, + "45,38": 1004, + "47,38": 1053, + "49,38": 1090, + "51,38": 1145, + "53,38": 1200, + "55,38": 1251, + "57,38": 1289, + "59,38": 1344, + "61,38": 1399, + "63,38": 1446, + "65,38": 1473, + "67,38": 1514, + "69,38": 1555, + "71,38": 1592, + "73,38": 1620, + "75,38": 1661, + "77,38": 1700, + "1,39": 8, + "3,39": 49, + "5,39": 90, + "9,39": 155, + "11,39": 196, + "13,39": 237, + "17,39": 299, + "19,39": 354, + "21,39": 409, + "25,39": 498, + "27,39": 553, + "29,39": 608, + "33,39": 694, + "35,39": 749, + "37,39": 804, + "41,39": 893, + "43,39": 948, + "45,39": 1003, + "49,39": 1089, + "51,39": 1144, + "53,39": 1199, + "57,39": 1288, + "59,39": 1343, + "61,39": 1398, + "65,39": 1472, + "67,39": 1513, + "69,39": 1554, + "73,39": 1619, + "75,39": 1660, + "77,39": 1699, + "1,40": 7, + "3,40": 48, + "5,40": 89, + "7,40": 127, + "9,40": 154, + "11,40": 195, + "13,40": 236, + "15,40": 272, + "17,40": 298, + "19,40": 353, + "21,40": 408, + "23,40": 460, + "25,40": 497, + "27,40": 552, + "29,40": 607, + "31,40": 657, + "33,40": 693, + "35,40": 748, + "37,40": 803, + "39,40": 855, + "41,40": 892, + "43,40": 947, + "45,40": 1002, + "47,40": 1052, + "49,40": 1088, + "51,40": 1143, + "53,40": 1198, + "55,40": 1250, + "57,40": 1287, + "59,40": 1342, + "61,40": 1397, + "63,40": 1445, + "65,40": 1471, + "67,40": 1512, + "69,40": 1553, + "71,40": 1591, + "73,40": 1618, + "75,40": 1659, + "77,40": 1698, + "1,41": 6, + "3,41": 47, + "5,41": 88, + "9,41": 153, + "11,41": 194, + "13,41": 235, + "17,41": 297, + "19,41": 352, + "21,41": 407, + "25,41": 496, + "27,41": 551, + "29,41": 606, + "33,41": 692, + "35,41": 747, + "37,41": 802, + "41,41": 891, + "43,41": 946, + "45,41": 1001, + "49,41": 1087, + "51,41": 1142, + "53,41": 1197, + "57,41": 1286, + "59,41": 1341, + "61,41": 1396, + "65,41": 1470, + "67,41": 1511, + "69,41": 1552, + "73,41": 1617, + "75,41": 1658, + "77,41": 1697, + "1,42": 5, + "2,42": 38, + "3,42": 46, + "4,42": 79, + "5,42": 87, + "6,42": 120, + "7,42": 126, + "8,42": 144, + "9,42": 152, + "10,42": 184, + "11,42": 193, + "12,42": 225, + "13,42": 234, + "14,42": 266, + "15,42": 271, + "16,42": 288, + "17,42": 296, + "18,42": 341, + "19,42": 351, + "20,42": 396, + "21,42": 406, + "22,42": 451, + "23,42": 459, + "24,42": 485, + "25,42": 495, + "26,42": 539, + "27,42": 550, + "28,42": 594, + "29,42": 605, + "30,42": 649, + "31,42": 656, + "32,42": 681, + "33,42": 691, + "34,42": 736, + "35,42": 746, + "36,42": 791, + "37,42": 801, + "38,42": 846, + "39,42": 854, + "40,42": 880, + "41,42": 890, + "42,42": 934, + "43,42": 945, + "44,42": 989, + "45,42": 1000, + "46,42": 1044, + "47,42": 1051, + "48,42": 1076, + "49,42": 1086, + "50,42": 1131, + "51,42": 1141, + "52,42": 1186, + "53,42": 1196, + "54,42": 1241, + "55,42": 1249, + "56,42": 1275, + "57,42": 1285, + "58,42": 1329, + "59,42": 1340, + "60,42": 1384, + "61,42": 1395, + "62,42": 1439, + "63,42": 1444, + "64,42": 1461, + "65,42": 1469, + "66,42": 1502, + "67,42": 1510, + "68,42": 1543, + "69,42": 1551, + "70,42": 1584, + "71,42": 1590, + "72,42": 1608, + "73,42": 1616, + "74,42": 1648, + "75,42": 1657, + "76,42": 1689, + "77,42": 1696, + "1,43": 4, + "3,43": 45, + "5,43": 86, + "9,43": 151, + "11,43": 192, + "13,43": 233, + "17,43": 295, + "19,43": 350, + "21,43": 405, + "25,43": 494, + "27,43": 549, + "29,43": 604, + "33,43": 690, + "35,43": 745, + "37,43": 800, + "41,43": 889, + "43,43": 944, + "45,43": 999, + "49,43": 1085, + "51,43": 1140, + "53,43": 1195, + "57,43": 1284, + "59,43": 1339, + "61,43": 1394, + "65,43": 1468, + "67,43": 1509, + "69,43": 1550, + "73,43": 1615, + "75,43": 1656, + "77,43": 1695, + "1,44": 3, + "2,44": 37, + "3,44": 44, + "4,44": 78, + "5,44": 85, + "6,44": 119, + "7,44": 125, + "8,44": 143, + "9,44": 150, + "10,44": 183, + "11,44": 191, + "12,44": 224, + "13,44": 232, + "14,44": 265, + "15,44": 270, + "16,44": 287, + "17,44": 294, + "18,44": 340, + "19,44": 349, + "20,44": 395, + "21,44": 404, + "22,44": 450, + "23,44": 458, + "24,44": 484, + "25,44": 493, + "26,44": 538, + "27,44": 548, + "28,44": 593, + "29,44": 603, + "30,44": 648, + "31,44": 655, + "32,44": 680, + "33,44": 689, + "34,44": 735, + "35,44": 744, + "36,44": 790, + "37,44": 799, + "38,44": 845, + "39,44": 853, + "40,44": 879, + "41,44": 888, + "42,44": 933, + "43,44": 943, + "44,44": 988, + "45,44": 998, + "46,44": 1043, + "47,44": 1050, + "48,44": 1075, + "49,44": 1084, + "50,44": 1130, + "51,44": 1139, + "52,44": 1185, + "53,44": 1194, + "54,44": 1240, + "55,44": 1248, + "56,44": 1274, + "57,44": 1283, + "58,44": 1328, + "59,44": 1338, + "60,44": 1383, + "61,44": 1393, + "62,44": 1438, + "63,44": 1443, + "64,44": 1460, + "65,44": 1467, + "66,44": 1501, + "67,44": 1508, + "68,44": 1542, + "69,44": 1549, + "70,44": 1583, + "71,44": 1589, + "72,44": 1607, + "73,44": 1614, + "74,44": 1647, + "75,44": 1655, + "76,44": 1688, + "77,44": 1694, + "1,45": 2, + "3,45": 43, + "5,45": 84, + "9,45": 149, + "11,45": 190, + "13,45": 231, + "17,45": 293, + "19,45": 348, + "21,45": 403, + "25,45": 492, + "27,45": 547, + "29,45": 602, + "33,45": 688, + "35,45": 743, + "37,45": 798, + "41,45": 887, + "43,45": 942, + "45,45": 997, + "49,45": 1083, + "51,45": 1138, + "53,45": 1193, + "57,45": 1282, + "59,45": 1337, + "61,45": 1392, + "65,45": 1466, + "67,45": 1507, + "69,45": 1548, + "73,45": 1613, + "75,45": 1654, + "77,45": 1693, + "11,46": 189, + "13,46": 230, + "27,46": 546, + "29,46": 601, + "43,46": 941, + "45,46": 996, + "59,46": 1336, + "61,46": 1391, + "75,46": 1653, + "77,46": 1692, + "1,47": 1, + "2,47": 36, + "3,47": 42, + "4,47": 77, + "5,47": 83, + "6,47": 118, + "7,47": 124, + "8,47": 142, + "9,47": 148, + "17,47": 292, + "18,47": 339, + "19,47": 347, + "20,47": 394, + "21,47": 402, + "22,47": 449, + "23,47": 457, + "24,47": 483, + "25,47": 491, + "33,47": 687, + "34,47": 734, + "35,47": 742, + "36,47": 789, + "37,47": 797, + "38,47": 844, + "39,47": 852, + "40,47": 878, + "41,47": 886, + "49,47": 1082, + "50,47": 1129, + "51,47": 1137, + "52,47": 1184, + "53,47": 1192, + "54,47": 1239, + "55,47": 1247, + "56,47": 1273, + "57,47": 1281, + "65,47": 1465, + "66,47": 1500, + "67,47": 1506, + "68,47": 1541, + "69,47": 1547, + "70,47": 1582, + "71,47": 1588, + "72,47": 1606, + "73,47": 1612 + }, + "total_path_cells": 1696, + "unique_path_ids": 1696 +} \ No newline at end of file diff --git a/run_algorithm.py b/run_algorithm.py new file mode 100644 index 0000000..6ae84d8 --- /dev/null +++ b/run_algorithm.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +import sys +import os +import signal +import logging +from typing import Optional + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from common.utils import setup_logging +from config.settings import ( + ALGORITHM_SERVER_HOST, ALGORITHM_SERVER_PORT, LOG_LEVEL, LOG_FILE, + RCS_SERVER_HOST, RCS_SERVER_PORT, MONITOR_POLLING_INTERVAL +) +from algorithm_system.algorithm_server import AlgorithmServer + + +class AlgorithmApplication: + + def __init__(self, host: str = None, port: int = None, log_level: str = None, + enable_path_monitor: bool = True, monitor_interval: float = None, + rcs_host: str = None, rcs_port: int = None): + # Initialize algorithm system application + self.host = host or ALGORITHM_SERVER_HOST + self.port = port or ALGORITHM_SERVER_PORT + self.log_level = log_level or LOG_LEVEL + self.enable_path_monitor = enable_path_monitor + self.monitor_interval = monitor_interval or MONITOR_POLLING_INTERVAL + self.rcs_host = rcs_host or RCS_SERVER_HOST + self.rcs_port = rcs_port or RCS_SERVER_PORT + + setup_logging(self.log_level, LOG_FILE) + self.logger = logging.getLogger(__name__) + + self.server: Optional[AlgorithmServer] = None + + signal.signal(signal.SIGINT, self.signal_handler) + signal.signal(signal.SIGTERM, self.signal_handler) + + def signal_handler(self, signum, frame): + # Handle system signals, shutdown program + self.logger.info(f"Received signal {signum}, shutting down algorithm system...") + self.shutdown() + sys.exit(0) + + def start_server(self): + # Start algorithm server + try: + self.logger.info("Initializing algorithm system server...") + + # Set RCS connection configuration + os.environ['RCS_SERVER_HOST'] = self.rcs_host + os.environ['RCS_SERVER_PORT'] = str(self.rcs_port) + + self.server = AlgorithmServer(self.host, self.port, self.enable_path_monitor, self.monitor_interval) + + status = self.server.get_server_status() + self.logger.info("Algorithm system server status:") + self.logger.info(f" - Host: {status['host']}") + self.logger.info(f" - Port: {status['port']}") + self.logger.info(f" - Path mapping loaded: {status['path_mapping_loaded']}") + self.logger.info(f" - AGV count: {status['agv_count']}") + self.logger.info(f" - Task allocation algorithm: {status['algorithms']['task_allocation']}") + self.logger.info(f" - Path planning algorithm: {status['algorithms']['path_planning']}") + + self.logger.info("Algorithm system server initialization completed") + + self.server.start_server() + + except KeyboardInterrupt: + self.logger.info("Received interrupt signal, shutting down algorithm server...") + except Exception as e: + self.logger.error(f"Algorithm server startup failed: {e}") + raise + finally: + self.shutdown() + + def shutdown(self): + # Shutdown algorithm system components + self.logger.info("Shutting down algorithm system...") + + if self.server: + try: + self.server.stop_server() + except Exception as e: + self.logger.error(f"Failed to shutdown server: {e}") + + self.logger.info("Algorithm system shutdown completed") + + +def main(): + # Main function entry point + import argparse + + parser = argparse.ArgumentParser(description="Algorithm System - Provides task allocation and path planning services") + parser.add_argument( + "--host", + default=ALGORITHM_SERVER_HOST, + help=f"Algorithm server host address (default: {ALGORITHM_SERVER_HOST})" + ) + parser.add_argument( + "--port", + type=int, + default=ALGORITHM_SERVER_PORT, + help=f"Algorithm server port (default: {ALGORITHM_SERVER_PORT})" + ) + parser.add_argument( + "--log-level", + choices=["DEBUG", "INFO", "WARNING", "ERROR"], + default="DEBUG", + help=f"Log level (default: DEBUG)" + ) + parser.add_argument( + "--task-algorithm", + choices=["NEAREST_FIRST", "LOAD_BALANCED", "PRIORITY_FIRST", "MULTI_OBJECTIVE"], + default="LOAD_BALANCED", + help="Task allocation algorithm (default: LOAD_BALANCED)" + ) + parser.add_argument( + "--path-algorithm", + choices=["A_STAR", "DIJKSTRA", "GREEDY"], + default="DIJKSTRA", + help="Path planning algorithm (default: DIJKSTRA)" + ) + parser.add_argument( + "--enable-path-monitor", + action="store_true", + default=True, + help="Enable path monitoring service (default: True)" + ) + parser.add_argument( + "--disable-path-monitor", + action="store_true", + help="Disable path monitoring service" + ) + parser.add_argument( + "--monitor-interval", + type=float, + default=MONITOR_POLLING_INTERVAL, + help=f"Path monitoring polling interval in seconds (default: {MONITOR_POLLING_INTERVAL})" + ) + parser.add_argument( + "--rcs-host", + default=RCS_SERVER_HOST, + help=f"RCS server host address (default: {RCS_SERVER_HOST})" + ) + parser.add_argument( + "--rcs-port", + type=int, + default=RCS_SERVER_PORT, + help=f"RCS server port (default: {RCS_SERVER_PORT})" + ) + + args = parser.parse_args() + + # Handle path monitoring switch + enable_path_monitor = args.enable_path_monitor and not args.disable_path_monitor + + app = AlgorithmApplication( + host=args.host, + port=args.port, + log_level=args.log_level, + enable_path_monitor=enable_path_monitor, + monitor_interval=args.monitor_interval, + rcs_host=args.rcs_host, + rcs_port=args.rcs_port + ) + + try: + print("=" * 60) + print("CTU Warehouse Management System - Algorithm System") + print("=" * 60) + print(f"Server Address: http://{args.host}:{args.port}") + print(f"Task Allocation Algorithm: {args.task_algorithm}") + print(f"Path Planning Algorithm: {args.path_algorithm}") + print(f"Log Level: {args.log_level}") + print(f"Path Monitoring Service: {'Enabled' if enable_path_monitor else 'Disabled'}") + if enable_path_monitor: + print(f"Monitor Polling Interval: {args.monitor_interval}s") + print(f"RCS Server Address: {args.rcs_host}:{args.rcs_port}") + print("=" * 60) + print("Available APIs:") + print(f" POST http://{args.host}:{args.port}/open/task/send/v1") + print(f" - Task allocation API") + print(f" - Supports new format: {{\"tasks\": [...], \"agvStatus\": [...]}}") + print(f" - Compatible with old format: [task1, task2, ...]") + print(f" POST http://{args.host}:{args.port}/open/path/plan/v1") + print(f" - Path planning API") + print(f" POST http://{args.host}:{args.port}/open/path/batch/plan/v1") + print(f" - Batch path planning API") + print(f" POST http://{args.host}:{args.port}/open/algorithm/config/v1") + print(f" - Algorithm configuration API") + if enable_path_monitor: + print(f" POST http://{args.host}:{args.port}/monitor/path/start/v1") + print(f" - Start path monitoring service") + print(f" POST http://{args.host}:{args.port}/monitor/path/stop/v1") + print(f" - Stop path monitoring service") + print(f" GET http://{args.host}:{args.port}/monitor/path/status/v1") + print(f" - Path monitoring service status query") + print(f" GET http://{args.host}:{args.port}/health") + print(f" - Health check API") + print("=" * 60) + print("Press Ctrl+C to stop server") + print() + + app.start_server() + + except KeyboardInterrupt: + print("\nReceived interrupt signal, shutting down...") + except Exception as e: + print(f"Startup failed: {e}") + sys.exit(1) + + +if __name__ == "__main__": + main() \ No newline at end of file -- Gitblit v1.9.1