wmeGisLBBOX

Determines which geographical divisions are in a Viewport intersect with the given BBOX.

Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta // @require https://update.greasyfork.org/scripts/542477/1623829/wmeGisLBBOX.js

// ==UserScript==
// @name                wmeGisLBBOX
// @namespace           https://github.com/JS55CT/
// @author              JS55CT
// @description         Determines which geographical divisions are in a Viewport intersect with the given BBOX.
// @version             2026.07.13.00
// @license             MIT
// @grant               GM_xmlhttpRequest
// @connect             github.io
// @match              *://this-library-is-not-supposed-to-run.com/*
// @contributionURL     https://github.com/WazeDev/Thank-The-Authors
// ==/UserScript==

var wmeGisLBBOX = (function () {
  // Constructor for the wmeGisLBBOX class
  const funcName = "wmeGisLBBOX";

  const BASE_URL = `https://WazeDev.github.io/wmeGisLBBOX/`;
  const BASE_URL_BBOX = `${BASE_URL}BBOX%20JSON/`;
  const BASE_URL_GEOJSON = `${BASE_URL}GEOJSON/`;

  function wmeGisLBBOX() {
    // Ensure class instantiation with 'new'
    if (!(this instanceof wmeGisLBBOX)) {
      return new wmeGisLBBOX();
    }
    this.cache = {}; // Cache for storing fetched JSON / geoJSON data
  }

  /**
   * Fetches JSON data from a specified URL, utilizing caching to store and reuse fetched data.
   *
   * This function makes an HTTP GET request to retrieve data from a given URL and employs caching
   * to improve efficiency by storing previously fetched results. It uses `GM_xmlhttpRequest` for
   * cross-domain requests when running in a userscript environment.
   *
   * Process Overview:
   * 1. Checks the cache for existing data associated with the URL.
   * 2. If cached data is available, returns it immediately.
   * 3. If no cache is found, performs an HTTP request to fetch the data.
   * 4. On successful fetch, parses the JSON, stores it in cache, and resolves with the data.
   * 5. Handles errors, rejecting the Promise with an appropriate error message if the fetch fails.
   *
   * @param {string} url - The URL from which to fetch JSON data.
   * @returns {Promise<Object>} - A Promise resolving to the parsed JSON data.
   **/
  wmeGisLBBOX.prototype.fetchJsonWithCache = function (url) {
    if (this.cache[url]) {
      return Promise.resolve(this.cache[url]);
    }
    // Fetch data using GM_xmlhttpRequest
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method: "GET",
        url: url,
        onload: (response) => {
          if (response.status >= 200 && response.status < 300) {
            // Parse and store fetched data in cache
            const data = JSON.parse(response.responseText);
            this.cache[url] = data;
            resolve(data);
          } else {
            reject(new Error(`${funcName}: Failed to fetch data from ${url}, status: ${response.status}`));
          }
        },
        onerror: (error) => {
          reject(new Error(`${funcName}: Failed to fetch data from ${url}, error: ${error}`));
        },
      });
    });
  };

  /**
   * Determines whether two bounding boxes intersect.
   *
   * This function checks for intersection between two bounding boxes `bbox1` and `bbox2`
   * by evaluating both latitude and longitude overlapping scenarios. Special handling is included
   * to manage cases where bounding boxes wrap the antimeridian (180 degrees longitude).
   *
   * Process Overview:
   * 1. Checks latitude intersection by evaluating if latitude ranges overlap.
   * 2. Checks longitude intersection, considering whether a bounding box wraps the antimeridian.
   * 3. Returns true if both latitude and longitude intersections occur; false otherwise.
   *
   * Longitude Intersection Scenarios:
   * - Standard check if neither bounding box wraps the antimeridian.
   * - Special checks for when one or both bounding boxes wrap the antimeridian.
   *
   * @param {Object} bbox1 - The first bounding box with properties minLat, maxLat, minLon, maxLon.
   * @param {Object} bbox2 - The second bounding box with properties minLat, maxLat, minLon, maxLon.
   * @returns {boolean} - True if the bounding boxes intersect, false otherwise.
   **/
  function checkIntersection(bbox1, bbox2) {
    // Check for Latitude Intersection
    const latIntersects = !(bbox1.maxLat < bbox2.minLat || bbox1.minLat > bbox2.maxLat);
    if (!latIntersects) {
      return false;
    }
    // Check for Longitude Intersection
    const bbox1Wraps = bbox1.minLon > bbox1.maxLon;
    const bbox2Wraps = bbox2.minLon > bbox2.maxLon;
    let lonIntersects = false;
    if (!bbox1Wraps && !bbox2Wraps) {
      // Neither box wraps the antimeridian - standard overlap check
      lonIntersects = !(bbox1.maxLon < bbox2.minLon || bbox1.minLon > bbox2.maxLon);
    } else if (bbox1Wraps && !bbox2Wraps) {
      // bbox1 wraps and bbox2 does not
      const separated = bbox1.maxLon < bbox2.minLon && bbox1.minLon > bbox2.maxLon;
      lonIntersects = !separated;
    } else if (!bbox1Wraps && bbox2Wraps) {
      // bbox2 wraps and bbox1 does not
      const separated = bbox2.maxLon < bbox1.minLon && bbox2.minLon > bbox1.maxLon;
      lonIntersects = !separated;
    } else {
      lonIntersects = true; // Both boxes wrap the antimeridian
    }
    return lonIntersects; // Return true if both latitude and longitude intersect
  }

  /**
   * Identifies countries intersecting with the specified viewport bounding box.
   *
   * This function fetches a list of countries defined by their bounding boxes,
   * checks for intersection with the current viewport, and returns details for
   * countries that intersect.
   *
   * Process Overview:
   * 1. Fetches country bounding box data from a specified JSON URL.
   * 2. Iterates over country data, checking each one's bounding boxes against the viewport.
   * 3. Collects information on countries that intersect, including ISO codes and names.
   *
   * viewportBbox: {minLon: number, minLat: number, maxLon: number, maxLat: number}
   *
   * Error Handling:
   * - Logs errors during the fetch process and returns an empty array if issues occur.
   *
   * @param {Object} viewportBbox - The bounding box defining the current viewport.
   * @returns {Array} - An array of objects representing countries intersecting with the viewport.
   **/
  wmeGisLBBOX.prototype.getIntersectingCountries = async function (viewportBbox) {
    const url = `${BASE_URL_BBOX}COUNTRIES_BBOX_ESPG4326.json`;
    return this.fetchJsonWithCache(url)
      .then((COUNTRY_DATA) => {
        const intersectingCountries = Object.keys(COUNTRY_DATA).flatMap((code) => {
          const countryData = COUNTRY_DATA[code];
          for (const bbox of countryData.bbox) {
            if (checkIntersection(bbox, viewportBbox)) {
              return [
                {
                  ISO_ALPHA2: countryData["ISO_ALPHA2"],
                  ISO_ALPHA3: countryData["ISO_ALPHA3"],
                  name: countryData["name"],
                  Sub_level: countryData["Sub_level"],
                  source: "BBOX",
                },
              ];
            }
          }
          return [];
        });

        return intersectingCountries;
      })
      .catch((error) => {
        console.error(`${funcName}: Error fetching country data:`, error);
        return [];
      });
  };

  /**
   * Fetches and augments country data with subdivision information.
   *
   * This asynchronous function retrieves base country information and then
   * iteratively fetches additional subdivision data for each country, augmenting
   * the country data with its respective subdivisions.
   *
   * @returns {Object} - A comprehensive data object containing country details and
   *                     corresponding first-level subdivisions.
   *
   * Process Overview:
   * 1. Fetches country data from a static JSON file.
   * 2. Iterates through each country and retrieves its subdivision JSON data.
   * 3. Adds the fetched subdivisions data to the country information structure.
   *
   * Error Handling:
   * - Logs an error if fetching either the country or subdivision data fails.
   * - Returns an empty object if the initial fetch fails.
   *
   * URLs:
   * - Subdivision Data: Dynamic URL based on country ISO_ALPHA3 code from the base URL.
   **/
  wmeGisLBBOX.prototype.getCountriesAndSubsJson = async function () {
    const url = `${BASE_URL_BBOX}COUNTRIES_BBOX_ESPG4326.json`;
    const funcName = "getCountriesAndSubsJson";

    try {
      // Fetch the country data
      const COUNTRY_DATA = await this.fetchJsonWithCache(url);

      if (!COUNTRY_DATA) {
        console.warn(`${funcName}: No country data found.`);
        return {};
      }
      // Iterate over each country to fetch and add subdivision data
      for (const countryCode in COUNTRY_DATA) {
        if (COUNTRY_DATA.hasOwnProperty(countryCode)) {
          // Fetch the first-level subdivision data for the country
          const isoAlpha3 = COUNTRY_DATA[countryCode].ISO_ALPHA3;
          const subL1Url = `${BASE_URL_BBOX}${isoAlpha3}/${isoAlpha3}_BBOX_ESPG4326.json`;
          try {
            const subL1Data = await this.fetchJsonWithCache(subL1Url);
            if (!subL1Data) {
              COUNTRY_DATA[countryCode].subL1 = {}; // Initialize as empty object if no data is found
            } else {
              // Add subdivisions to the country data
              COUNTRY_DATA[countryCode].subL1 = subL1Data;
            }
          } catch (subError) {
            COUNTRY_DATA[countryCode].subL1 = {}; // Safely initialize as empty object in case of error
          }
        }
      }
      return COUNTRY_DATA;
    } catch (error) {
      console.warn(`${funcName}: Error fetching country data:`, error);
      return {};
    }
  };

  /**
   * Cleans intersecting country data by pruning empty subdivisions across different levels.
   *
   * This function processes country data that intersects with a given geographic area,
   * handling hierarchical subdivision structures and removing any empty or null subdivisions.
   * It dynamically adapts to the subdivision level identified for each country.
   *
   * Process Overview:
   * 1. Iterates through intersecting country data.
   * 2. Applies cleaning logic based on subdivision levels (subLevel) present.
   * 3. Specifically handles multi-level subdivisions, including special logic for deep levels.
   * 4. Recursively prunes subdivisions that lack content, ensuring only relevant data remains.
   *
   * Cleaning Logic by Subdivision Level:
   * - Sub_Level = 1: Retains countries and sub-level 1 if they contain valid subdivisions.
   * - Sub_Level = 2: Further inspects sub-level 2; if empty, removes it and reveals sub-level 1 if needed.
   * - Sub_Level = 3: Examines sub-level 3 within applicable countries, removing them if empty and ensuring sub-level 2 remains relevant.
   * - Undetected or Sub_Level = 0: Completely removes countries with unidentified subdivisions.
   *
   * @param {Object} intersectingCountries - Object containing intersecting country data.
   *
   * Modifications:
   * - Directly alters the `intersectingCountries` object by removing empty subdivisions.
   * - Ensures structural integrity by retaining populated subdivisions and appropriate country entries.
   */
  wmeGisLBBOX.prototype.cleanIntersectingData = function (intersectingCountries) {
    for (const countryName in intersectingCountries) {
      const country = intersectingCountries[countryName];
      const subLevel = country.Sub_level || 0; // Default to 0 if Sub_level is not provided
      // Handle Sub_level = 0: Return the country directly
      if (subLevel === 0) {
        continue; // This means we should retain the country as is without further checks
      }
      // Proceed with cleanup based on sub-level hierarchy
      if (subLevel >= 3) {
        for (const sub1Name in country.subL1) {
          const subL1 = country.subL1[sub1Name];
          for (const sub2Name in subL1.subL2) {
            const subL2 = subL1.subL2[sub2Name];
            if (!subL2.subL3 || Object.keys(subL2.subL3).length === 0) {
              delete subL1.subL2[sub2Name];
            }
          }
        }
      }
      if (subLevel >= 2) {
        for (const sub1Name in country.subL1) {
          const subL1 = country.subL1[sub1Name];
          if (!subL1.subL2 || Object.keys(subL1.subL2).length === 0) {
            delete country.subL1[sub1Name];
          }
        }
      }
      if (subLevel >= 1) {
        if (!country.subL1 || Object.keys(country.subL1).length === 0) {
          delete intersectingCountries[countryName];
        }
      }
    }
  };

  /**
   * Fetches GeoJSON data for a specified region and checks for intersection with a given viewport.
   *
   * This asynchronous function retrieves GeoJSON data based on the specified country, subdivision,
   * and sub-subdivision codes. It then checks whether the geometries in the GeoJSON intersect
   * with the provided viewport bounding box, returning intersection results or the full GeoJSON data.
   *
   * Process Overview:
   * 1. Constructs the URL for the GeoJSON file based on provided codes.
   * 2. Fetches the GeoJSON data using the constructed URL.
   * 3. Defines the viewport area as a polygon using the specified bounding box coordinates.
   * 4. Iterates through GeoJSON features, checking if their geometries intersect with the viewport.
   * 5. Returns either the full GeoJSON data or a boolean indicating intersection presence based on a flag.
   *
   * Supported Geometry Types:
   * - Polygon: Directly checks for intersection with the viewport polygon.
   * - MultiPolygon: Iterates through component polygons to check for intersection.
   * - Logs a warning if any unsupported geometry types are encountered.
   *
   * Error Handling:
   * - Logs an error message if fetching or processing the GeoJSON fails, returns `false`.
   *
   * viewportBbox: {minLon: number, minLat: number, maxLon: number, maxLat: number}
   *
   * @param {string} countyCode - The country code corresponding to the GeoJSON data.
   * @param {string} subCode - The subdivision code for the GeoJSON data.
   * @param {string} subSubCode - The sub-subdivision code for the GeoJSON data.
   * @param {Object} viewportBbox - The bounding box of the viewport.
   * @param {boolean} [returnGeoJson=false] - Flag to determine if the full GeoJSON data should be returned.
   * @returns {boolean|Object} - Returns true or false for intersection presence, or the full GeoJSON data.
   **/
  wmeGisLBBOX.prototype.fetchAndCheckGeoJsonIntersection = async function (countyCode, subCode, subSubCode, viewportBbox, returnGeoJson = false) {
    const url = `${BASE_URL_GEOJSON}${countyCode}/${subCode}/${countyCode}-${subCode}-${subSubCode}_EPSG4326.geojson`;

    try {
      const geoJsonData = await this.fetchJsonWithCache(url);
      // Define the viewport as a polygon.
      const viewportPolygon = [
        [viewportBbox.minLon, viewportBbox.minLat],
        [viewportBbox.minLon, viewportBbox.maxLat],
        [viewportBbox.maxLon, viewportBbox.maxLat],
        [viewportBbox.maxLon, viewportBbox.minLat],
        [viewportBbox.minLon, viewportBbox.minLat],
      ];

      // Iterate through each feature in the GeoJSON data
      for (const feature of geoJsonData.features) {
        const featureGeometry = feature.geometry;
        // Check if the geometry type is Polygon or MultiPolygon
        if (featureGeometry.type === "Polygon") {
          for (const polygon of featureGeometry.coordinates) {
            if (hasIntersection(polygon, viewportPolygon)) {
              return returnGeoJson ? geoJsonData : true; // Return the GeoJSON data or intersection boolean
            }
          }
        } else if (featureGeometry.type === "MultiPolygon") {
          for (const multiPolygon of featureGeometry.coordinates) {
            for (const polygon of multiPolygon) {
              if (hasIntersection(polygon, viewportPolygon)) {
                return returnGeoJson ? geoJsonData : true; // Return the GeoJSON data or intersection boolean
              }
            }
          }
        } else {
          console.warn(`${funcName}: Unsupported geometry type:`, featureGeometry.type);
          continue; // Skip unsupported geometry types
        }
      }
      return false; // No intersection found
    } catch (error) {
      console.error(`${funcName}: Error fetching or processing GeoJSON from ${url}:`, error);
      return false;
    }
  };

  /**
   * Identifies US states and counties that intersect with the given viewport bounding box.
   *
   * This asynchronous function fetches state and county bounding box data, identifies which states
   * and counties intersect with a specified viewport, and optionally checks for more precise
   * intersections using GeoJSON data.
   *
   * Process Overview:
   * 1. Retrieves bounding box data for US states.
   * 2. Checks each state's bounding box against the viewport for intersections.
   * 3. For intersecting states, fetches county bounding box data and identifies intersecting counties.
   * 4. If `highPrecision` is true, uses GeoJSON data to refine intersection checks.
   * 5. Constructs an object that maps intersecting states to their respective intersecting counties and subdivisions.
   *
   * Optional Features:
   * - High precision mode uses GeoJSON data to confirm intersections and update data sources.
   * - Supports returning GeoJSON data if intersections occur and `returnGeoJson` is true.
   *
   * viewportBbox: {minLon: number, minLat: number, maxLon: number, maxLat: number}
   *
   * Error Handling:
   * - Logs a message if fetching or processing bounding box or GeoJSON data fails.
   *
   * @param {Object} viewportBbox - The bounding box of the viewport.
   * @param {boolean} [highPrecision=false] - Flag to activate high precision intersection checks using GeoJSON.
   * @param {boolean} [returnGeoJson=false] - Flag to indicate if GeoJSON data should be returned when intersecting.
   * @returns {Object} - A structured object detailing intersecting states, counties, and subdivisions.
   **/
  wmeGisLBBOX.prototype.getIntersectingStatesAndCounties = async function (viewportBbox, highPrecision = false, returnGeoJson = false) {
    const STATES_URL = `${BASE_URL_BBOX}USA/USA_BBOX_ESPG4326.json`;
    const intersectingRegions = {};

    try {
      const US_States = await this.fetchJsonWithCache(STATES_URL);
      for (const stateCode in US_States) {
        const stateData = US_States[stateCode];
        if (checkIntersection(stateData.bbox, viewportBbox)) {
          const intersectingCounties = {};
          const countiesUrl = `${BASE_URL_BBOX}USA/USA-${stateCode}_BBOX_ESPG4326.json`;
          const countiesData = await this.fetchJsonWithCache(countiesUrl);
          for (const countyEntry of countiesData) {
            const countyName = Object.keys(countyEntry)[0];
            const countyData = countyEntry[countyName];
            if (checkIntersection(countyData.bbox, viewportBbox)) {
              const intersectingSubCounties = countyData.subdivisions
                .filter((sub) => sub.bbox && checkIntersection(sub.bbox, viewportBbox))
                .reduce((acc, sub) => {
                  acc[sub.name] = {
                    subL3_num: sub.sub_num,
                    source: "BBOX",
                  };
                  return acc;
                }, {});
              if (Object.keys(intersectingSubCounties).length > 0) {
                let source = "BBOX";
                let geoJsonData = null;
                if (highPrecision) {
                  const intersectsGeoJson = await this.fetchAndCheckGeoJsonIntersection("USA", stateCode, countyData.sub_num, viewportBbox, returnGeoJson);
                  if (returnGeoJson && intersectsGeoJson) {
                    geoJsonData = intersectsGeoJson;
                    source = "GEOJSON";
                  } else if (intersectsGeoJson) {
                    source = "GEOJSON";
                  } else {
                    continue;
                  }
                }
                intersectingCounties[countyName] = {
                  subL2_num: countyData.sub_num,
                  subL3: intersectingSubCounties,
                  source: source,
                };
                if (geoJsonData) {
                  intersectingCounties[countyName].geoJsonData = geoJsonData;
                }
              }
            }
          }
          if (Object.keys(intersectingCounties).length > 0) {
            let stateSource = "BBOX";
            if (highPrecision) {
              const anyCountyWithGeoJson = Object.values(intersectingCounties).some((county) => county.source === "GEOJSON");
              if (anyCountyWithGeoJson) {
                stateSource = "GEOJSON";
              }
            }
            intersectingRegions[stateData.name] = {
              subL1_id: stateData.sub_id,
              subL1_num: stateData.sub_num,
              subL2: intersectingCounties,
              source: stateSource,
            };
          }
        }
      }
    } catch (error) {
      console.error(`${funcName}: Failed to fetch or process data:`, error);
    }
    return intersectingRegions;
  };

  /**
   * Retrieves and identifies subdivisions that intersect with a given viewport bounding box.
   *
   * This asynchronous function fetches hierarchical subdivision data for a specified country based
   * on the subdivisions level (`Sub_level`). It checks for intersections at both first and second subdivision levels
   * (if applicable) and returns structured data detailing the intersecting areas.
   *
   * Process Overview:
   * 1. Constructs the URL for the country's first-level subdivision data.
   * 2. Fetches and checks first-level subdivisions for intersection with the viewport.
   * 3. For intersecting first-level subdivisions and if `Sub_level` is 2, constructs URLs for second-level data.
   * 4. Checks second-level subdivisions for intersection and integrate results into the output.
   * 5. Builds and returns a comprehensive object of intersecting subdivisions.
   *
   * Parameters:
   * - `viewportBbox`: {minLon: number, minLat: number, maxLon: number, maxLat: number}
   * Represents the geographic viewport for intersection checks.
   *
   * Error Handling:
   * - Logs warnings if no first-level or second-level data are found.
   * - Logs errors for issues with fetching or processing subdivisions.
   *
   * @param {Object} countryObj - An object containing details about the country, including ISO code and Sub_level.
   * @param {Object} viewportBbox - The bounding box of the viewport to check against.
   * @returns {Object} - An object containing details of intersecting subdivisions, organized by hierarchy.
   */
  wmeGisLBBOX.prototype.getIntersectingSubdivisions = async function (countryObj, viewportBbox) {
    const subdivisionsResult = {};
    const countryCode = countryObj.ISO_ALPHA3;
    const subL1Url = `${BASE_URL_BBOX}${countryCode}/${countryCode}_BBOX_ESPG4326.json`;
    const funcName = "getIntersectingSubdivisions";
    try {
      // Check Sub_Level to decide how to handle subdivisions
      if (!countryObj.Sub_level) {
        return subdivisionsResult; // Return {} if Sub_Level is 0 or undefined
      }
      if (countryObj.Sub_level >= 1) {
        // Fetch first-level subdivision data
        const subL1Data = await this.fetchJsonWithCache(subL1Url);
        if (!subL1Data) {
          console.warn(`${funcName}: No first-level subdivision data found for country code: ${countryCode}`);
          return subdivisionsResult;
        }
        for (const subdivisionID in subL1Data) {
          const subdivision = subL1Data[subdivisionID];
          // Check intersection for first-level subdivisions
          if (checkIntersection(subdivision.bbox, viewportBbox)) {
            const subdivisionName = subdivision["name"];
            // Initialize data for this subdivision
            subdivisionsResult[subdivisionName] = {
              subL1_num: subdivision["sub_num"],
              subL1_id: subdivisionID,
              source: "BBOX",
              subL2: {}, // Placeholder for second-level subdivisions
            };
            // If Sub_Level is 2, fetch second-level subdivision data
            if (countryObj.Sub_level === 2) {
              const subL2Url = `${BASE_URL_BBOX}${countryCode}/${countryCode}-${subdivisionID}_BBOX_ESPG4326.json`;
              try {
                const subL2Data = await this.fetchJsonWithCache(subL2Url);
                if (!subL2Data) {
                  console.warn(`${funcName}: No second-level subdivision data found for ${subdivisionName} (${subdivisionID}) in country: ${countryCode}`);
                } else {
                  // Process second-level subdivisions
                  for (const subsubDivision of subL2Data) {
                    const subsubID = Object.keys(subsubDivision)[0];
                    const subsub = subsubDivision[subsubID];
                    // Check intersection for second-level subdivisions
                    if (checkIntersection(subsub.bbox, viewportBbox)) {
                      const subsubName = subsub["name"];
                      // Add intersecting second-level subdivisions
                      subdivisionsResult[subdivisionName].subL2[subsubName] = {
                        subL2_num: subsub["sub_num"],
                        source: "BBOX",
                      };
                    }
                  }
                }
              } catch (subError) {
                console.warn(`${funcName}: Error fetching second-level data for ${subdivisionName} (${subdivisionID}) in country: ${countryCode} - ${subError.message}`);
              }
            } // End of Sub_Level 2 check
          }
        }
      }
    } catch (error) {
      console.error(`${funcName}: Error fetching or processing subdivisions for country code ${countryCode} - ${error.message}`);
    }
    return subdivisionsResult;
  };

  /**
   * Determines regions within the viewport by identifying intersecting countries and their subdivisions.
   *
   * This asynchronous function analyzes a given viewport bounding box to determine which countries
   * and internal subdivisions, such as states and counties, are visible. It accommodates the USA
   * specifically and uses different handling for other countries.
   *
   * Process Overview:
   * 1. Fetches countries that intersect with the viewport.
   * 2. If no intersecting countries are found, logs a warning.
   * 3. For the USA, retrieves intersecting states and counties; for other countries, retrieves subdivisions.
   * 4. Cleans up intersecting data to ensure only relevant information is retained.
   * 5. Constructs a results object that maps visible countries to their intersecting subdivisions.
   *
   * Features:
   * - Supports high precision checks using GeoJSON data if specified.
   * - Integration of subdivision logic for both the USA and other countries.
   *
   * viewportBbox: {minLon: number, minLat: number, maxLon: number, maxLat: number}
   *
   * Error Handling:
   * - Logs errors encountered during the fetching and processing of intersecting regions.
   *
   * @param {Object} viewportBbox - The bounding box defining the current viewport.
   * @param {boolean} [highPrecision=false] - Flag to use high precision intersection checks.
   * @param {boolean} [returnGeoJson=false] - Flag to determine if GeoJSON data should be returned.
   * @returns {Object} - Structured results identifying regions within the viewport.
   **/
  wmeGisLBBOX.prototype.whatsInView = async function (viewportBbox, highPrecision = false, returnGeoJson = false) {
    const results = {};
    const funcName = "whatsInView";
    try {
      const countries = await this.getIntersectingCountries(viewportBbox);
      if (!countries || Object.keys(countries).length === 0) {
        console.warn(`${funcName}: Viewport does not intersect with any known countries.`);
        return results;
      }
      for (const countryCode in countries) {
        const country = countries[countryCode];
        // Handle the case when Sub_level is 0
        if (country.Sub_level === 0) {
          results[country.name] = {
            ISO_ALPHA2: country.ISO_ALPHA2,
            ISO_ALPHA3: country.ISO_ALPHA3,
            Sub_level: country.Sub_level,
            source: country.source,
            subL1: {}, // Leave subL1 empty when Sub_level is 0
          };
          continue;
        }
        if (country.ISO_ALPHA3 === "USA") {
          const statesAndCounties = await this.getIntersectingStatesAndCounties(viewportBbox, highPrecision, returnGeoJson);
          if (statesAndCounties && Object.keys(statesAndCounties).length > 0) {
            results[country.name] = {
              ISO_ALPHA2: country.ISO_ALPHA2,
              ISO_ALPHA3: country.ISO_ALPHA3,
              Sub_level: country.Sub_level,
              subL1: statesAndCounties,
            };
            this.cleanIntersectingData(results);
          }
        } else {
          const subdivisions = await this.getIntersectingSubdivisions(country, viewportBbox);
          if (subdivisions && Object.keys(subdivisions).length > 0) {
            results[country.name] = {
              ISO_ALPHA2: country.ISO_ALPHA2,
              ISO_ALPHA3: country.ISO_ALPHA3,
              Sub_level: country.Sub_level,
              subL1: subdivisions,
            };
            this.cleanIntersectingData(results);
          }
        }
      }
    } catch (error) {
      console.error(`${funcName}: Error during finding intersecting regions:`, error);
    }
    return results;
  };

  /**
   * Determines if a given point is inside a polygon using the ray-casting algorithm.
   *
   * This function checks whether a point, defined by its coordinates, is inside a polygon.
   * The polygon is represented by an array of vertices (points), and the function uses the
   * ray-casting technique to toggle the state whenever the ray crosses a polygon edge.
   *
   * Process Overview:
   * 1. Iterates over each edge of the polygon using vertex pairs (xi, yi) and (xj, yj).
   * 2. Uses logical conditions to check if a horizontal ray extending from the point intersects
   *    with the polygon's edge.
   * 3. Toggles 'inside' state whenever an intersection is detected, flipping between true and false.
   * 4. Returns the final 'inside' state, indicating whether the point lies within the polygon.
   *
   * @param {Array} point - An array [x, y] representing the coordinates of the point to test.
   * @param {Array} vs - An array of vertices, where each vertex is represented as [x, y].
   * @returns {boolean} - True if the point is inside the polygon, false otherwise.
   **/
  function isPointInPolygon(point, vs) {
    const [x, y] = point;
    let inside = false;
    for (let i = 0, j = vs.length - 1; i < vs.length; j = i++) {
      const [xi, yi] = vs[i];
      const [xj, yj] = vs[j];
      const intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
      if (intersect) inside = !inside;
    }
    return inside;
  }

  /**
   * Calculates the intersection point, if any, between two line segments defined by endpoints.
   *
   * This function checks whether two line segments, one defined by (p1, p2) and the other by (q1, q2),
   * intersect. The method utilizes linear algebra to derive intersection coordinates and subsequently
   * verifies that the intersection falls within the bounds of both segments.
   *
   * Process Overview:
   * 1. Computes coefficients for the line equations using segment endpoints.
   * 2. Determines if the lines are parallel (denominator = 0), in which case no intersection exists.
   * 3. Calculates potential intersection coordinates using determinant-based formulae.
   * 4. Verifies if the intersection coordinates lie within the bounds of both line segments.
   * 5. Returns the intersection point if valid, or null if no intersection exists within segment limits.
   *
   * @param {Array} p1 - [x, y] coordinates of the first endpoint of the first segment.
   * @param {Array} p2 - [x, y] coordinates of the second endpoint of the first segment.
   * @param {Array} q1 - [x, y] coordinates of the first endpoint of the second segment.
   * @param {Array} q2 - [x, y] coordinates of the second endpoint of the second segment.
   * @returns {Array|null} - [x, y] coordinates of the intersection point, or null if no intersection.
   **/
  function segmentIntersection(p1, p2, q1, q2) {
    // Calculate coefficients
    const a1 = p2[1] - p1[1];
    const b1 = p1[0] - p2[0];
    const c1 = a1 * p1[0] + b1 * p1[1];

    const a2 = q2[1] - q1[1];
    const b2 = q1[0] - q2[0];
    const c2 = a2 * q1[0] + b2 * q1[1];

    const denominator = a1 * b2 - a2 * b1;

    if (denominator === 0) {
      return null; // Parallel lines
    }

    const intersectX = (b2 * c1 - b1 * c2) / denominator;
    const intersectY = (a1 * c2 - a2 * c1) / denominator;

    // Check if the intersection is within the bounds of the line segments
    const withinBounds = (value, end1, end2) => Math.min(end1, end2) <= value && value <= Math.max(end1, end2);

    if (withinBounds(intersectX, p1[0], p2[0]) && withinBounds(intersectY, p1[1], p2[1]) && withinBounds(intersectX, q1[0], q2[0]) && withinBounds(intersectY, q1[1], q2[1])) {
      return [intersectX, intersectY];
    }

    return null; // Intersection point is not within the line segments
  }

  /**
   * Checks for intersection between two polygons.
   *
   * This function determines if two polygons intersect by examining:
   * 1. If any edges from `polygon1` intersect with any edges from `polygon2`.
   * 2. If any point from `polygon1` is contained within `polygon2`.
   * 3. If any point from `polygon2` is contained within `polygon1`.
   *
   * The function uses helper methods `segmentIntersection` to check edge intersections
   * and `isPointInPolygon` to evaluate point containment.
   *
   * Process Overview:
   * - Iterates over edges of both polygons, checking for intersection using the `segmentIntersection` function.
   * - Verifies point containment using the `isPointInPolygon` function to see if any point from one polygon is inside the other.
   * - Returns true if any intersection or containment is found; otherwise, returns false.
   *
   * @param {Array} polygon1 - An array of points defining the first polygon, where each point is an {x, y} object.
   * @param {Array} polygon2 - An array of points defining the second polygon, where each point is an {x, y} object.
   * @returns {boolean} - True if an intersection or containment is found between the polygons, false otherwise.
   **/
  function hasIntersection(polygon1, polygon2) {
    // Check each edge of polygon1 against each edge of polygon2
    for (let i = 0; i < polygon1.length - 1; i++) {
      for (let j = 0; j < polygon2.length - 1; j++) {
        const intersection = segmentIntersection(polygon1[i], polygon1[i + 1], polygon2[j], polygon2[j + 1]);

        if (intersection) {
          return true; // An intersection is found
        }
      }
    }

    // Check if any point of polygon1 is inside polygon2
    for (const point of polygon1) {
      if (isPointInPolygon(point, polygon2)) {
        return true; // A contained point is found
      }
    }

    // Check if any point of polygon2 is inside polygon1
    for (const point of polygon2) {
      if (isPointInPolygon(point, polygon1)) {
        return true; // A contained point is found
      }
    }

    return false; // No intersection found
  }

  return wmeGisLBBOX;
})();