diff --git a/.gitignore b/.gitignore index 8f3fd26..bcfea77 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .venv __pycache__ *.png -images \ No newline at end of file +images +trajectories +z diff --git a/autopilot.py b/autopilot.py index 4bf6cf0..f43542b 100644 --- a/autopilot.py +++ b/autopilot.py @@ -197,12 +197,6 @@ class AutoPilot(Pilot): self.timer.stop() return PilotCommand(processing_time=self.timer.get_elapsed()) - # Расстояние до цели - distance_to_target = math.sqrt( - (self.points[self.target_idx][0] - self.pos.x) ** 2 + - (self.points[self.target_idx][1] - self.pos.y) ** 2 - ) - # Вычисляем оптический поток для покадрового сравнения matching_timer = Timer() matching_timer.start() @@ -246,6 +240,16 @@ class AutoPilot(Pilot): chunk_timer.stop() print(f"Chunk timer: {chunk_timer.get_elapsed() * 1000:.2f} ms") + command = self.make_command() + self.timer.reset() + return command + + def make_command(self) -> PilotCommand: + # Расстояние до цели + distance_to_target = math.sqrt( + (self.points[self.target_idx][0] - self.pos.x) ** 2 + + (self.points[self.target_idx][1] - self.pos.y) ** 2 + ) if distance_to_target < 35: self.target_idx += 1 @@ -267,8 +271,6 @@ class AutoPilot(Pilot): angle_trajectory = self.pos.yaw + math.pi / 2 - # print("[ANGLE]", angle_trajectory, "->", target_angle) - # Вычисляем разность углов (направление поворота) angle_diff = target_angle - angle_trajectory @@ -284,7 +286,6 @@ class AutoPilot(Pilot): max(min(d_a_limit, angle_diff), -d_a_limit), d_r, False, self.timer.get_elapsed() ) - self.timer.reset() return command def reset_position(self, x: float = 0.0, y: float = 0.0, angle: float = 0.0): diff --git a/constants.py b/constants.py index 12e925e..92194ca 100644 --- a/constants.py +++ b/constants.py @@ -1,5 +1,12 @@ import numpy as np +# Ширина 1 пикселя в метрах +YANDEX_PIXEL_RATIO_18 = 0.332697807435653 +GOOGLE_PIXEL_RATIO_15 = 2766 / 1031 +GOOGLE_PIXEL_RATIO_18 = 346 / 1031 + +WINDOW_HEIGHT = 1031 + # Ширина и высота снимка в пикселях CHUNK_WIDTH = 700 diff --git a/google_map.py b/google_map.py new file mode 100644 index 0000000..8f68f27 --- /dev/null +++ b/google_map.py @@ -0,0 +1,83 @@ + +import cv2 +import math +import numpy as np +import os +import utility + +from io import BytesIO +from PIL import Image +from selenium.webdriver.common.actions.wheel_input import ScrollOrigin +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.common.action_chains import ActionChains +from time import sleep + +def generateURL(lat: float, lon: float, zoom: int): + return f"https://www.google.com/maps/@{lon},{lat},{zoom}z" + +class GoogleMap: + initial_zoom: int + initial_lat: float + initial_lon: float + + def __init__(self, initial_lat=49.103814, initial_lon=55.794258, initial_zoom=18): + self.initial_lat = initial_lat + self.initial_lon = initial_lon + self.initial_zoom = initial_zoom + + options = webdriver.ChromeOptions() + # options.add_experimental_option("detach", True) + self.driver = webdriver.Chrome(options) + self.driver.get(generateURL(initial_lat, initial_lon, initial_zoom)) + self.driver.maximize_window() + + action = ActionChains(self.driver) + sleep(5) + self.driver.execute_script('document.querySelector(\'.yHc72.qk5Wte\').click()') + sleep(5) + + def open(self, lat, lon, zoom): + self.driver.get(generateURL(lat, lon, zoom)) + + def save_photo(self, filename: str): + im = self.make_screenshot() + im.save(filename) + + def destroy(self): + self.driver.close() + + def get_size(self) -> tuple[int, int]: + html = self.driver.find_element(By.TAG_NAME, 'html') + return (html.size['width'], html.size['height']) + + def scroll(self, x: float, y: float, count: int = 1, inner_zoom: bool = True): + html = self.driver.find_element(By.TAG_NAME, 'html') + + x_offset = (x - 0.5) * (html.size['width'] - 72) + 72 + y_offset = (y - 0.5) * html.size['height'] + action = ActionChains(self.driver) + + for i in range(count-1): + action.scroll_from_origin(ScrollOrigin(html, int(x_offset), int(y_offset)), 0, -100 if inner_zoom else 100) + action.perform() + if i != count - 1: + sleep(0.25) + + def move(self, dx: float, dy: float): + self.driver.execute_script(utility.google_map_js_move_script(dx, dy)) + + def make_as_center(self, x: float, y: float): + dx = (x - 0.5) * self.get_size()[0] + dy = (0.5 - y) * self.get_size()[1] + self.move(dx, dy) + sleep(1) + + def make_screenshot(self) -> Image.Image: + png = self.driver.get_screenshot_as_png() + im = Image.open(BytesIO(png)) + return utility.cv2_to_pil(np.array(im)[:, 72:]) + + def get_geolocation(self): + current_url = self.driver.current_url + return utility.parse_google_maps_url(current_url) diff --git a/main.py b/main.py index 9ccb457..de89542 100644 --- a/main.py +++ b/main.py @@ -1,23 +1,28 @@ +from google_map import GoogleMap from pathlib import Path +from position import Position from simulator import Simulator from time import sleep from trajectory_drawer import TrajectoryDrawer +from utility import cv2_to_pil +from vision_chunk import VisionChunk from visualization import VisualizationManager from yandex_map import YandexMap -from vision_chunk import VisionChunk -from utility import cv2_to_pil -import random +import argparse +import autopilot +import constants import cv2 import matplotlib.pyplot as plt import numpy as np +import pickle +import random +import utility -import autopilot - -def make_global_photo(filename): - yandexMap = YandexMap() - yandexMap.save_photo(filename) - yandexMap.destroy() +def make_global_photo(filename, initial_zoom=13): + google_map = GoogleMap(initial_zoom=initial_zoom) + google_map.save_photo(filename) + google_map.destroy() def get_trajectory_points(bg_img: str) -> list[(float, float)]: trajectoryDrawer = TrajectoryDrawer(bg_img) @@ -26,97 +31,111 @@ def get_trajectory_points(bg_img: str) -> list[(float, float)]: points = list(map(lambda p: [p[0] / trajectoryDrawer.img.shape[1], p[1] / trajectoryDrawer.img.shape[0]], trajectoryDrawer.points)) return points -def main(): - # Скриншот местности - # make_global_photo('map.jpg') +def build(name: str): - # Получаем траекторию от пользователя - # points = get_trajectory_points('map.jpg') + # Создание папки с информацией о маршруте + dir = Path('trajectories') + if not dir.exists(): dir.mkdir() + dir /= name + assert not dir.exists() + dir.mkdir() + dir_chunks = dir / 'chunks' + dir_chunks.mkdir() - # Trajectory #1 - # points = [[np.float64(0.5384504359393909), np.float64(0.4084520767967683)], [np.float64(0.4451750568707629), np.float64(0.38213330305374654)], [np.float64(0.49266070439660997), np.float64(0.2789637099811013)], [np.float64(0.36377108968359656), np.float64(0.3263375027185404)], [np.float64(0.3535955937852008), np.float64(0.4337180995900692)]] - - # Trajectory #2 - # points = [[np.float64(0.29197731306713737), np.float64(0.3452870198135161)], [np.float64(0.33494051797147517), np.float64(0.2010601397017569)], [np.float64(0.39768940934491587), np.float64(0.25369768718780034)], [np.float64(0.4027771572941138), np.float64(0.4158213334448144)], [np.float64(0.2914120077394487), np.float64(0.5547844588079692)]] - - # Trajectory #3 - # points = [[np.float64(0.2755834585641664), np.float64(0.45687862048392835)], [np.float64(0.295934450360958), np.float64(0.5021469113219258)], [np.float64(0.32872215936689997), np.float64(0.4810918923275084)], [np.float64(0.3649017003389739), np.float64(0.5295184360146684)], [np.float64(0.3999506306556705), np.float64(0.49477765467387963)]] - - # Trajectory #4 - # points = [[np.float64(0.42143223310783934), np.float64(0.6663760594783815)], [np.float64(0.4253893704016599), np.float64(0.5537317078582484)], [np.float64(0.5124463908657128), np.float64(0.5621537154560153)], [np.float64(0.5124463908657128), np.float64(0.6684815613778233)], [np.float64(0.42143223310783934), np.float64(0.6663760594783815)]] - - # Trajectory #5 - # points = [[np.float64(0.5983728006743884), np.float64(0.7348048712102382)], [np.float64(0.5966768846913225), np.float64(0.5453097002604814)], [np.float64(0.6345523416464622), np.float64(0.7190136069644251)], [np.float64(0.6402053949233488), np.float64(0.5495207040593649)], [np.float64(0.5983728006743884), np.float64(0.7348048712102382)]] - - # Trajectory #6 - # points = [[np.float64(0.4406526142492536), np.float64(0.28106921188054296)], [np.float64(0.38581799746345413), np.float64(0.2968604761263561)], [np.float64(0.3931669667234066), np.float64(0.353709027411283)], [np.float64(0.4248240650739713), np.float64(0.35265627646156217)], [np.float64(0.40616898926024564), np.float64(0.3179154951207735)]] - - # Trajectory #7 - # points = [[np.float64(0.5491912371654754), np.float64(0.7505961354560512)], [np.float64(0.5537136797869846), np.float64(0.6863783275230781)], [np.float64(0.5017055896396284), np.float64(0.6653233085286606)], [np.float64(0.5520177638039186), np.float64(0.6042637534448503)], [np.float64(0.5593667330638712), np.float64(0.516885424618018)]] - - # Trajectory #8 - # points = - - # Trajectory #9 - # points = - - # Trajectory #10 - # points = - - print(points) - - # Для каждой точки сделаем приближенный снимок - yandexMap = YandexMap() - chunks: list[VisionChunk] = [] - - plt.ion() - for i in range(len(points)): - point = points[i] - yandexMap.scroll(point[0], point[1], 5, True) - sleep(1) - cv2_img = yandexMap.make_screenshot(point[0], point[1], 0.2, 0.2) - img = cv2_to_pil(cv2_img) - chunk = VisionChunk(img) - Path('chunks').mkdir(exist_ok=True) - chunk.save_image(Path('.') / 'chunks' / f'chunk_{i}.png') - plt.subplot(1, len(points), i+1) - plt.imshow(img) - plt.pause(0.25) - yandexMap.scroll(point[0], point[1], 5, False) - - plt.tight_layout() - - # Выделим на каждой картинке ключевые точки - for i in range(len(points)): - chunk = VisionChunk.load_image(Path('chunks') / f'chunk_{i}.png') - chunks.append(chunk) - kp, des = chunk.compute_keypoints() - - plt.subplot(1, len(points), i+1) - plt.imshow(chunk.image) - kp_coords = np.array([j.pt for j in kp]) - if len(kp_coords) > 0: - plt.scatter(kp_coords[:, 0], kp_coords[:, 1], c='red', s=20, alpha=0.7, marker='o') - plt.pause(0.2) - plt.ioff() - - plt.show(block=True) + # make_global_photo('map.jpg', 15) + points = get_trajectory_points('map.jpg') + google_map = GoogleMap(initial_zoom=15) # Начнём симуляцию полёта с первой точки - yandexMap.scroll(points[0][0], points[0][1], 5, True) - sleep(0.2) - yandexMap.make_as_center(*points[0]) - sleep(1) + google_map.make_as_center(*points[0]) + sleep(3) + google_map.scroll(0.5, 0.5, 10, True) + sleep(2) + geo = google_map.get_geolocation() + google_map.open(geo['lat'], geo['lon'], 18) + sleep(20) - vis_manager = VisualizationManager() - width, height = yandexMap.get_size() - # print(width, height) + width, height = google_map.get_size() points_coords = np.array(list(map(lambda p: [ (p[0] - points[0][0]) * width, (points[0][1] - p[1]) * height ], points))) - points_coords *= 2 ** 4 - pilot = autopilot.AutoPilot(points_coords, chunks, vis_manager) - simulator = Simulator(yandexMap) + + points_coords *= constants.GOOGLE_PIXEL_RATIO_15 + points_coords_pixel = points_coords / constants.GOOGLE_PIXEL_RATIO_18 + + pilot = autopilot.AutoPilot(points_coords_pixel) + simulator = Simulator(google_map) + pilot.target_idx = 0 + + pilot.pos = simulator.pos.copy() + command = pilot.make_command() + + positions: list[Position] = [] + + print("points_coords_pixel:", points_coords_pixel) + for i in range(100000): + + if command.stop: + break + + # simulator.handle(command.dangle, command.velocity) + chunk = simulator.get_chunk() + pilot.pos = simulator.pos.copy() + command = pilot.make_command() + command.velocity /= constants.GOOGLE_PIXEL_RATIO_18 + + print("Position:", simulator.pos) + + # Save Image + chunk.save_image(dir_chunks / f"chunk_{len(positions)}.png") + positions.append(simulator.pos) + + simulator.handle(command.dangle, command.velocity) + sleep(1.5) + + data = { + 'points': points_coords, + 'chunk_positions': positions, + 'initial_geolocation': geo + } + + file_positions = dir / 'positions.pkl' + with file_positions.open('wb') as file: + pickle.dump(data, file) + + with file_positions.open('rb') as file: + r = pickle.load(file) + print(r) + + sleep(15) + google_map.destroy() + +def run(name: str): + dir = Path('trajectories') + assert dir.exists() + dir /= name + assert dir.exists() + dir_chunks = dir / 'chunks' + file_positions = dir / 'positions.pkl' + + with file_positions.open('rb') as file: + data = pickle.load(file) + + initial_geolocation = data['initial_geolocation'] + chunks: list[VisionChunk] = [] + for i in range(len(data['chunk_positions'])): + chunk = VisionChunk.load_image(dir_chunks / f"chunk_{i}.png") + chunk.pos = data['chunk_positions'][i] + + points = data['points'] / constants.YANDEX_PIXEL_RATIO_18 + + yandex_map = YandexMap(initial_geolocation['lat'], initial_geolocation['lon'], 18) + sleep(10) + + vis_manager = VisualizationManager() + width, height = yandex_map.get_size() + pilot = autopilot.AutoPilot(points, chunks, vis_manager) + simulator = Simulator(yandex_map) pilot.target_idx = 0 chunk = simulator.get_chunk() @@ -125,7 +144,7 @@ def main(): vis_manager.update_display() vis_manager.pause(1) - vis_manager.set_target_points(points_coords) + vis_manager.set_target_points(points) proc_time = np.array([]) @@ -157,6 +176,7 @@ def main(): # simulator.handle(command.dangle, command.velocity) chunk = simulator.get_chunk() command = pilot.handle(chunk) + command.velocity /= constants.YANDEX_PIXEL_RATIO_18 proc_time = np.append(proc_time, command.proccessing_time) @@ -194,7 +214,47 @@ def main(): print("Average FPS:", 1 / proc_time.mean()) vis_manager.show_final() -if __name__ == "__main__": - main() -#TODO +def main(mode: str, name: str): + if name is None: + name = utility.generate_folder_name() + + if mode == 'build' or mode == 'standalone': + build(name) + + if mode == 'run' or mode == 'standalone': + run(name) + +def parse_args(): + """Парсер аргументов командной строки""" + parser = argparse.ArgumentParser(description='Обработка траекторий') + + # Добавляем обязательный аргумент --mode + parser.add_argument( + '--mode', + type=str, + required=True, + choices=['standalone', 'build', 'run'], + help='Режим работы: standalone, build или run' + ) + + # Добавляем опциональный аргумент --name + parser.add_argument( + '--name', + type=str, + help='Название траектории (обязательно для режимов build и run)' + ) + + # Парсим аргументы + args = parser.parse_args() + + # Проверяем, что для build и run указан --name + if args.mode in ['build', 'run'] and not args.name: + parser.error(f"--name обязателен для режима {args.mode}") + + return args + +if __name__ == "__main__": + args = parse_args() + main(args.mode, args.name) + diff --git a/map.jpg b/map.jpg index 24f1bec..761ddf4 100644 Binary files a/map.jpg and b/map.jpg differ diff --git a/position.py b/position.py index 3cc897b..7fcf58e 100644 --- a/position.py +++ b/position.py @@ -42,13 +42,14 @@ class Position: f"roll={math.degrees(self.roll):.1f}°)" ) - def get_homography_matrix(self, K: np.ndarray = constants.K, sliding: bool = True) -> np.ndarray: + def get_homography_matrix(self, K_in: np.ndarray = constants.K, K_out: np.ndarray | None = None, sliding: bool = True) -> np.ndarray: """ Возвращает матрицу гомографии """ R = self.get_rotation_matrix() T = self.get_translation_matrix() if not sliding: T[0, 2] = T[1, 2] = 0 - return K @ R @ T @ np.linalg.inv(K) + if K_out is None: K_out = K_in + return K_out @ R @ T @ np.linalg.inv(K_in) def copy(self) -> 'Position': """Создает полную копию объекта""" diff --git a/simulator.py b/simulator.py index deb5ebe..f75c7b2 100644 --- a/simulator.py +++ b/simulator.py @@ -8,12 +8,13 @@ import numpy as np from position import Position from vision_chunk import VisionChunk from yandex_map import YandexMap +from google_map import GoogleMap import constants import utility class Simulator: - def __init__(self, yandex_map: YandexMap = None): - self.yandex_map = yandex_map + def __init__(self, online_map: YandexMap | GoogleMap = None): + self.online_map = online_map # Используем новый конструктор с yaw, pitch, roll self.pos = Position(x=0, y=0, z=1, yaw=0, pitch=0, roll=0) @@ -35,17 +36,19 @@ class Simulator: Возвращает квадратное изображение 700x700. """ img_array = np.array(image) - print(img_array.shape) h, w, _ = img_array.shape # Применяем трансформацию pos = self.pos.copy() pos.x = 0 pos.y = 0 - K = utility.calc_camera_matrix(w, h) - K = constants.K - img_array = img_array[:constants.CHUNK_WIDTH, :constants.CHUNK_WIDTH] - transformed = cv2.warpPerspective(img_array, pos.get_homography_matrix(K), (constants.CHUNK_WIDTH, constants.CHUNK_WIDTH)) + + K_in = utility.calc_camera_matrix(w, h) + K_out = constants.K + H = pos.get_homography_matrix(K_in, K_out) + + shape = (constants.CHUNK_WIDTH, constants.CHUNK_WIDTH) + transformed = cv2.warpPerspective(img_array, H, shape) return Image.fromarray(transformed) @@ -60,13 +63,6 @@ class Simulator: dangle - изменение угла курса (радианы) velocity - скорость движения """ - from selenium.webdriver.common.by import By - from selenium.webdriver.common.action_chains import ActionChains - - html = self.yandex_map.driver.find_element(By.TAG_NAME, 'html') - action = ActionChains(self.yandex_map.driver) - action.move_to_element_with_offset(html, 200, 200) - action.click_and_hold() # Обновляем yaw в объекте Position self.pos.yaw += dangle @@ -77,10 +73,7 @@ class Simulator: dy = math.sin(math.pi / 2 + self.pos.yaw) * velocity / self.pos.z self.update_trajectory(dx, dy) - - action.move_by_offset(-dx, dy) - action.release() - action.perform() + self.online_map.move(dx, dy) def set_zoom(self, zoom_level: float): """Программное изменение масштаба""" @@ -88,8 +81,7 @@ class Simulator: def get_chunk(self) -> VisionChunk: """Получить текущий снимок с камеры дрона""" - png = self.yandex_map.driver.get_screenshot_as_png() - im = Image.open(BytesIO(png)) + im = self.online_map.make_screenshot() # Применяем перспективную трансформацию transformed_im = self._apply_perspective_transform(im) diff --git a/utility.py b/utility.py index 667b173..6af1800 100644 --- a/utility.py +++ b/utility.py @@ -1,7 +1,9 @@ from PIL import Image +from datetime import datetime +import constants import cv2 import numpy as np -import constants +import re def cv2_to_pil(cv_image: np.ndarray) -> Image.Image: """ @@ -19,7 +21,7 @@ def get_normals(H: np.ndarray, R: np.ndarray, T: np.ndarray) -> np.ndarray: n = cv2.decomposeHomographyMat(H, constants.K, R, T) return n -def estimate_transformation_matrix(src_pts: np.ndarray, dst_pts: np.ndarray) -> tuple[np.ndarray, float | None]: +def estimate_transformation_matrix(src_pts: np.ndarray, dst_pts: np.ndarray) -> np.ndarray | None: """Оценивает матрицу трансформации на основе сопоставленных точек""" # Используем RANSAC для оценки матрицы гомографии H, _ = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 3.0, maxIters=1000) @@ -32,3 +34,112 @@ def calc_camera_matrix(w: float, h: float): [0, f, h / 2], [0, 0, 1] ]) + + +def generate_folder_name(): + """ + Генерирует название для папки с текущей датой и временем до секунд. + Формат: YYYY-MM-DD_HH-MM-SS + """ + now = datetime.now() + folder_name = now.strftime("%Y-%m-%d_%H-%M-%S") + return folder_name + + + + + + + + + + +def parse_google_maps_url(url): + """ + Парсит URL Google Maps и извлекает lat, lon и zoom + Формат: /@lat,lon,zoom[m|z] + """ + pattern = r'/@([-\d.]+),([-\d.]+),(\d+)([mz])' + match = re.search(pattern, url) + + if match: + lon = float(match.group(1)) + lat = float(match.group(2)) + zoom_value = int(match.group(3)) + zoom_unit = match.group(4) + + return { + 'lat': lat, + 'lon': lon, + 'zoom': zoom_value, + 'zoom_unit': zoom_unit + } + return None + +def google_map_js_move_script(dx, dy) -> str: + return """ +async function sleep(ms) { + return new Promise((resolve, reject) => { + setTimeout(() => resolve(), ms); + }); +} + +async function simulateDrag(element, offsetX, offsetY) { + const rect = element.getBoundingClientRect(); + const startX = rect.left + rect.width / 2; + const startY = rect.top + rect.height / 2; + const step = 7 + const endX = startX + offsetX + step; + const endY = startY + offsetY + step; + + element.dispatchEvent(new MouseEvent('mousedown', { + view: window, + bubbles: true, + cancelable: true, + clientX: startX, + clientY: startY, + button: 0 + })); + + let currentX = startX; + let currentY = startY; + + function move(stepX, stepY) { + currentX += stepX; + currentY += stepY; + + document.dispatchEvent(new MouseEvent('mousemove', { + view: window, + bubbles: true, + cancelable: false, + clientX: currentX, + clientY: currentY + })); + } + + await sleep(50); + move(step, step) + + while (currentX != endX || currentY != endY) { + await sleep(50); + const stepX = Math.min(step, Math.max(-step, endX - currentX)); + const stepY = Math.min(step, Math.max(-step, endY - currentY)); + move(stepX, stepY); + } + + await sleep(50) + document.dispatchEvent(new MouseEvent('mouseup', { + view: window, + bubbles: true, + cancelable: false, + clientX: endX, + clientY: endY, + button: 0 + })); +} + +{ + const canvas = document.querySelector('canvas.H1VXrf.JRr1M.DnOnV'); +""" + f"simulateDrag(canvas, {-dx}, {dy});" + """ +} +""" \ No newline at end of file diff --git a/vision_chunk.py b/vision_chunk.py index 2b7ed56..0351465 100644 --- a/vision_chunk.py +++ b/vision_chunk.py @@ -1,10 +1,11 @@ import cv2 import json import numpy as np +from PIL import Image from dataclasses import dataclass, field from pathlib import Path +from position import Position from typing import Literal, Optional, Tuple -from PIL import Image FeatureMethod = Literal["orb", "sift", "akaze", "brisk"] DEFAULT_METHOD = "orb" @@ -14,6 +15,7 @@ class VisionChunk: image: Image.Image feature_method: FeatureMethod = DEFAULT_METHOD + pos: Optional[Position] = field(default=None, init=False) keypoints: Optional[list] = field(default=None, init=False) descriptors: Optional[np.ndarray] = field(default=None, init=False) _detector: Optional[cv2.Feature2D] = field(default=None, init=False, repr=False) @@ -25,7 +27,7 @@ class VisionChunk: if self.feature_method == "orb": self._detector = cv2.ORB_create( - nfeatures=1000, + nfeatures=10000, scaleFactor=1.2, nlevels=32, edgeThreshold=31, diff --git a/yandex_map.py b/yandex_map.py index f0874ea..de279d5 100644 --- a/yandex_map.py +++ b/yandex_map.py @@ -1,16 +1,16 @@ -import math -from io import BytesIO -from time import sleep -import os - -import cv2 -import numpy as np - from PIL import Image +from io import BytesIO from selenium.webdriver.common.actions.wheel_input import ScrollOrigin from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.action_chains import ActionChains +from time import sleep + +import cv2 +import math +import numpy as np +import os +import utility def generateURL(lat: float, lon: float, zoom: int): return f"https://yandex.ru/maps/43/kazan/?l=sat&ll={lat}%2C{lon}&z={zoom}" @@ -68,51 +68,22 @@ class YandexMap: if i != count - 1: sleep(0.25) - def make_as_center(self, x: float, y: float): + def move(self, dx: float, dy: float): html = self.driver.find_element(By.TAG_NAME, 'html') - action = ActionChains(self.driver) action.move_to_element_with_offset(html, 0, 0) action.click_and_hold() - - dx = (x - 0.5) * self.get_size()[0] - dy = (0.5 - y) * self.get_size()[1] - print(dx, dy) action.move_by_offset(-dx, dy) action.release() action.perform() - def make_screenshot(self, x: float, y: float, width: float, height: float) -> cv2.typing.MatLike: - # Сохраняем скриншот - self.save_photo("temp.png") - - # Загружаем изображение - image = cv2.imread("temp.png") - - if image is None: - raise ValueError("Не удалось загрузить изображение temp.png") - - # Получаем размеры исходного изображения - img_height, img_width = image.shape[:2] - - # Преобразуем относительные координаты в абсолютные пиксели - center_x = int(x * img_width) - center_y = int(y * img_height) - crop_width = int(width * img_width) - crop_height = int(height * img_height) - - # Вычисляем координаты прямоугольника для кадрирования - x1 = max(0, center_x - crop_width // 2) - y1 = max(0, center_y - crop_height // 2) - x2 = min(img_width, center_x + crop_width // 2) - y2 = min(img_height, center_y + crop_height // 2) - - # Проверяем, что прямоугольник имеет положительные размеры - if x2 <= x1 or y2 <= y1: - raise ValueError("Некорректные размеры для кадрирования") - - # Кадрируем изображение - cropped_image = image[y1:y2, x1:x2] - - # Если нужно вернуть изображение как результат функции: - return cropped_image + + def make_as_center(self, x: float, y: float): + dx = (x - 0.5) * self.get_size()[0] + dy = (0.5 - y) * self.get_size()[1] + self.move(dx, dy) + + def make_screenshot(self) -> Image.Image: + png = self.driver.get_screenshot_as_png() + im = Image.open(BytesIO(png)) + return utility.cv2_to_pil(np.array(im)[:, :]) \ No newline at end of file