CoolFace
Apppublic

Omniscient001/Omniscient

sourceHugging Faceupdated 1y agoView on Hugging Face
1likes
mapcrunch_controller.py313 linesDownload Raw Back to root
1import time2from typing import Dict, Optional, List3 4import undetected_chromedriver as uc5from selenium.webdriver.support.ui import WebDriverWait6from selenium.webdriver.support import expected_conditions as EC7from selenium.webdriver.common.by import By8 9from config import MAPCRUNCH_URL, SELECTORS, DATA_COLLECTION_CONFIG10 11 12class MapCrunchController:13    def __init__(self, headless: bool = False):14        options = uc.ChromeOptions()15        options.add_argument(16            "user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"17        )18        options.add_argument("--window-size=1920,1080")19        options.set_capability("goog:loggingPrefs", {"browser": "ALL"})20 21        if headless:22            options.add_argument("--headless=new")23 24        self.driver = uc.Chrome(options=options, use_subprocess=True)25        self.wait = WebDriverWait(self.driver, 10)26 27        # Here we are injecting a script to the page to disable the browser detection.28        # Basically, we are setting the badBrowser property to 0, which is a property that is used to detect if the browser is being controlled by a script.29        # In the main.min.js, we can see some js code like this:30        # if (badBrowser) {31        #     alert("Unsupported browser!");32        # } else {33        #     window.panorama = { ... }34        # }35        self.driver.execute_cdp_cmd(36            "Page.addScriptToEvaluateOnNewDocument",37            {38                "source": """39                Object.defineProperty(window, 'badBrowser', {40                  value: 0,41                  writable: false,42                  configurable: false43                });44                window.alert = function() {};45                Object.defineProperty(navigator, 'webdriver', {46                  get: () => undefined47                });48            """49            },50        )51 52        for retry in range(3):53            try:54                self.driver.get(MAPCRUNCH_URL)55                time.sleep(3)56                break57            except Exception as e:58                if retry == 2:59                    raise e60                print(f"Failed to load MapCrunch, retry {retry + 1}/3")61                time.sleep(2)62 63    def setup_clean_environment(self):64        """65        Minimal environment setup using hideLoc() and hiding major UI.66        """67        self.driver.execute_script("if(typeof hideLoc === 'function') hideLoc();")68        self.driver.execute_script("""69            const menu = document.querySelector('#menu');70            if (menu) menu.style.display = 'none';71                                   72            const social = document.querySelector('#social');73            if (social) social.style.display = 'none';74                                   75            const googleImg = document.querySelector('img[alt="Google"]');76            if (googleImg && googleImg.parentElement) {77                googleImg.parentElement.style.display = 'none';78            }79 80            const topBar = document.querySelector('#topbar');81            if (topBar) topBar.style.display = 'none';82 83            const bottomBox = document.querySelector('#bottom-box');84            if (bottomBox) bottomBox.style.display = 'none';85            86            const infoFirstView = document.querySelector('#info-firstview');87            if (infoFirstView) infoFirstView.style.display = 'none';88 89            const controlsToHide = document.querySelectorAll('.gm-style-cc'); controlsToHide.forEach(el => { el.style.display = 'none'; });90            const keyboardButton = document.querySelector('button[aria-label="Keyboard shortcuts"]'); if (keyboardButton) { keyboardButton.style.display = 'none'; }91 92 93        """)94 95    def label_arrows_on_screen(self):96        """Overlays 'UP' and 'DOWN' labels on the navigation arrows."""97        try:98            pov = self.driver.execute_script("return window.panorama.getPov();")99            links = self.driver.execute_script("return window.panorama.getLinks();")100        except Exception:101            return102 103        if not links or not pov:104            return105 106        current_heading = pov["heading"]107        forward_link = None108        backward_link = None109 110        # This logic is identical to your existing `move` function111        # to ensure stylistic and behavioral consistency.112        min_forward_diff = 360113        for link in links:114            diff = 180 - abs(abs(link["heading"] - current_heading) - 180)115            if diff < min_forward_diff:116                min_forward_diff = diff117                forward_link = link118 119        target_backward_heading = (current_heading + 180) % 360120        min_backward_diff = 360121        for link in links:122            diff = 180 - abs(abs(link["heading"] - target_backward_heading) - 180)123            if diff < min_backward_diff:124                min_backward_diff = diff125                backward_link = link126 127        js_script = """128            document.querySelectorAll('.geobot-arrow-label').forEach(el => el.remove());129            document.querySelectorAll('path[data-geobot-modified]').forEach(arrow => {130                arrow.setAttribute('transform', arrow.getAttribute('data-original-transform') || '');131                arrow.removeAttribute('data-geobot-modified');132                arrow.removeAttribute('data-original-transform');133            });134 135            const modifyAndLabelArrow = (panoId, labelText, color) => {136                const arrowElement = document.querySelector(`path[pano="${panoId}"]`);137                if (!arrowElement) return;138 139                const originalTransform = arrowElement.getAttribute('transform') || '';140                arrowElement.setAttribute('data-original-transform', originalTransform);141                arrowElement.setAttribute('transform', `${originalTransform} scale(1.8)`);142                arrowElement.setAttribute('data-geobot-modified', 'true');143 144                const rect = arrowElement.getBoundingClientRect();145                const label = document.createElement('div');146                label.className = 'geobot-arrow-label';147                label.style.position = 'fixed';148                label.style.left = `${rect.left + rect.width / 2}px`;149                label.style.top = `${rect.top - 45}px`;150                label.style.transform = 'translateX(-50%)';151                label.style.padding = '5px 15px';152                label.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';153                label.style.color = color;154                label.style.borderRadius = '8px';155                label.style.fontSize = '28px';156                label.style.fontWeight = 'bold';157                label.style.zIndex = '99999';158                label.style.pointerEvents = 'none';159                label.innerText = labelText;160                document.body.appendChild(label);161            };162 163            const forwardPano = arguments[0];164            const backwardPano = arguments[1];165 166            if (forwardPano) {167                modifyAndLabelArrow(forwardPano, 'UP', '#76FF03');168            }169            if (backwardPano && backwardPano !== forwardPano) {170                modifyAndLabelArrow(backwardPano, 'DOWN', '#F44336');171            }172        """173 174        forward_pano = forward_link["pano"] if forward_link else None175        backward_pano = backward_link["pano"] if backward_link else None176 177        self.driver.execute_script(js_script, forward_pano, backward_pano)178        time.sleep(0.2)179 180    def get_available_actions(self) -> List[str]:181        """182        Checks for movement links via JavaScript.183        """184        base_actions = ["PAN_LEFT", "PAN_RIGHT", "GUESS"]185        links = self.driver.execute_script("return window.panorama.getLinks();")186        if links and len(links) > 0:187            base_actions.extend(["MOVE_FORWARD", "MOVE_BACKWARD"])188        return base_actions189 190    def get_current_address(self) -> Optional[str]:191        try:192            address_element = self.wait.until(193                EC.visibility_of_element_located(194                    (By.CSS_SELECTOR, SELECTORS["address_element"])195                )196            )197            address_text = address_element.text.strip()198            address_title = address_element.get_attribute("title") or ""199            return (200                address_title201                if len(address_title) > len(address_text)202                else address_text203            )204        except Exception:205            return "Stealth Mode"206 207    def pan_view(self, direction: str, degrees: int = 45):208        """Pans the view using a direct JS call."""209        pov = self.driver.execute_script("return window.panorama.getPov();")210        if direction == "left":211            pov["heading"] -= degrees212        elif direction == "right":213            pov["heading"] += degrees214        self.driver.execute_script("window.panorama.setPov(arguments[0]);", pov)215        time.sleep(0.5)216 217    def move(self, direction: str):218        """Moves by finding the best panorama link and setting it via JS."""219        pov = self.driver.execute_script("return window.panorama.getPov();")220        links = self.driver.execute_script("return window.panorama.getLinks();")221        if not links:222            return223 224        current_heading = pov["heading"]225        best_link = None226 227        if direction == "forward":228            min_diff = 360229            for link in links:230                diff = 180 - abs(abs(link["heading"] - current_heading) - 180)231                if diff < min_diff:232                    min_diff = diff233                    best_link = link234        elif direction == "backward":235            target_heading = (current_heading + 180) % 360236            min_diff = 360237            for link in links:238                diff = 180 - abs(abs(link["heading"] - target_heading) - 180)239                if diff < min_diff:240                    min_diff = diff241                    best_link = link242 243        if best_link:244            self.driver.execute_script(245                "window.panorama.setPano(arguments[0]);", best_link["pano"]246            )247            time.sleep(2.5)248 249    def select_map_location_and_guess(self, lat: float, lon: float):250        """Minimalist guess confirmation."""251        self.driver.execute_script(252            "document.querySelector('#bottom-box').style.display = 'block';"253        )254        self.wait.until(255            EC.element_to_be_clickable((By.CSS_SELECTOR, SELECTORS["go_button"]))256        ).click()257        time.sleep(0.5)258        self.wait.until(259            EC.element_to_be_clickable((By.CSS_SELECTOR, SELECTORS["confirm_button"]))260        ).click()261        time.sleep(3)262 263    def get_ground_truth_location(self) -> Optional[Dict[str, float]]:264        """Directly gets location from JS object."""265        return self.driver.execute_script("return window.loc;")266 267    def click_go_button(self) -> bool:268        self.wait.until(269            EC.element_to_be_clickable((By.CSS_SELECTOR, SELECTORS["go_button"]))270        ).click()271        time.sleep(DATA_COLLECTION_CONFIG.get("wait_after_go", 3))272        return True273 274    def take_street_view_screenshot(self) -> Optional[bytes]:275        pano_element = self.wait.until(276            EC.presence_of_element_located(277                (By.CSS_SELECTOR, SELECTORS["pano_container"])278            )279        )280        return pano_element.screenshot_as_png281 282    def load_location_from_data(self, location_data: Dict) -> bool:283        pano_id, pov = location_data.get("pano_id"), location_data.get("pov")284        if pano_id and pov:285            self.driver.execute_script(286                "window.panorama.setPano(arguments[0]); window.panorama.setPov(arguments[1]);",287                pano_id,288                pov,289            )290            time.sleep(2)291            return True292        return False293 294    def close(self):295        if self.driver:296            self.driver.quit()297 298    def __enter__(self):299        return self300 301    def __exit__(self, exc_type, exc_val, exc_tb):302        self.close()303 304    def load_url(self, url):305        """Load a specific MapCrunch URL."""306        try:307            self.driver.get(url)308            time.sleep(2)  # Wait for the page to load309            return True310        except Exception as e:311            print(f"Error loading URL: {e}")312            return False313