UNPKG

geo-toolkits

Version:

A comprehensive TypeScript library for geospatial operations including geofencing, polygon containment, area calculations, and coordinate processing. Supports GeoJSON, KML, and KMZ file formats with both local file and remote URL loading capabilities.

295 lines (294 loc) 12.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FileLoader = void 0; const fs = __importStar(require("fs/promises")); const path = __importStar(require("path")); const node_fetch_1 = __importDefault(require("node-fetch")); const errors_1 = require("../utils/errors"); class FileLoader { /** * Load file content from either URL or file path */ static load(source) { return __awaiter(this, void 0, void 0, function* () { // Validate source input if (!source || typeof source !== "string" || source.trim().length === 0) { throw new errors_1.FileLoadError("Source path/URL cannot be empty", "INVALID_SOURCE"); } const cleanSource = source.trim(); const isUrl = this.isUrl(cleanSource); if (isUrl) { return this.loadFromUrl(cleanSource); } else { return this.loadFromFile(cleanSource); } }); } /** * Load file from URL with comprehensive error handling */ static loadFromUrl(url) { return __awaiter(this, void 0, void 0, function* () { try { // Validate URL format const urlObj = new URL(url); if (urlObj.protocol !== "http:" && urlObj.protocol !== "https:") { throw new errors_1.FileLoadError(`Unsupported protocol: ${urlObj.protocol}. Only HTTP and HTTPS are supported.`, "UNSUPPORTED_PROTOCOL"); } const controller = new AbortController(); const timeout = 30000; // Set up timeout const timeoutId = setTimeout(() => { controller.abort(); }, timeout); const response = yield (0, node_fetch_1.default)(url, { signal: controller.signal, headers: { "User-Agent": "geo-toolkits/1.0 (GIS Library)", Accept: "application/json, application/xml, application/octet-stream, */*", "Accept-Encoding": "gzip, deflate", }, follow: 5, // Allow up to 5 redirects compress: true, }); clearTimeout(timeoutId); // Check response status if (!response.ok) { let errorMessage = `HTTP ${response.status}: ${response.statusText}`; // Provide more specific error messages for common status codes switch (response.status) { case 404: errorMessage = `File not found at URL: ${url}`; break; case 403: errorMessage = `Access forbidden to URL: ${url}`; break; case 401: errorMessage = `Authentication required for URL: ${url}`; break; case 500: errorMessage = `Server error when accessing URL: ${url}`; break; case 503: errorMessage = `Service unavailable for URL: ${url}`; break; } throw new errors_1.FileLoadError(errorMessage, "HTTP_ERROR"); } // Get the content const arrayBuffer = yield response.arrayBuffer(); const buffer = Buffer.from(arrayBuffer); return buffer; } catch (error) { if (error.name === "AbortError") { throw new errors_1.FileLoadError(`Request timeout after 30000ms. Try increasing the timeout or check your internet connection.`, "TIMEOUT_ERROR"); } if (error instanceof errors_1.FileLoadError) { throw error; } // Handle network errors if (error.code === "ENOTFOUND") { throw new errors_1.FileLoadError(`DNS resolution failed for URL: ${url}. Please check the URL and your internet connection.`, "DNS_ERROR"); } if (error.code === "ECONNREFUSED") { throw new errors_1.FileLoadError(`Connection refused to URL: ${url}. The server may be down or unreachable.`, "CONNECTION_REFUSED"); } if (error.code === "ECONNRESET") { throw new errors_1.FileLoadError(`Connection reset while downloading from URL: ${url}. Please try again.`, "CONNECTION_RESET"); } throw new errors_1.FileLoadError(`Failed to load from URL "${url}": ${error.message}`, "URL_LOAD_ERROR"); } }); } /** * Load file from local file system with comprehensive validation */ static loadFromFile(filePath) { return __awaiter(this, void 0, void 0, function* () { try { // Normalize and resolve the file path const resolvedPath = path.resolve(filePath); // Check if file exists and get stats let stats; try { stats = yield fs.stat(resolvedPath); } catch (statError) { if (statError.code === "ENOENT") { throw new errors_1.FileLoadError(`File not found: ${resolvedPath}`, "FILE_NOT_FOUND"); } throw statError; } // Ensure it's a file (not a directory) if (!stats.isFile()) { throw new errors_1.FileLoadError(`Path is not a file: ${resolvedPath}`, "NOT_A_FILE"); } // Check if file is empty if (stats.size === 0) { throw new errors_1.FileLoadError(`File is empty: ${resolvedPath}`, "EMPTY_FILE"); } // Check file permissions try { yield fs.access(resolvedPath, fs.constants.R_OK); } catch (accessError) { throw new errors_1.FileLoadError(`Permission denied reading file: ${resolvedPath}`, "PERMISSION_DENIED"); } // Read the file const fileBuffer = yield fs.readFile(resolvedPath); return fileBuffer; } catch (error) { if (error instanceof errors_1.FileLoadError) { throw error; } // Handle specific file system errors switch (error.code) { case "ENOENT": throw new errors_1.FileLoadError(`File not found: ${filePath}`, "FILE_NOT_FOUND"); case "EACCES": throw new errors_1.FileLoadError(`Permission denied: ${filePath}`, "PERMISSION_DENIED"); case "EISDIR": throw new errors_1.FileLoadError(`Path is a directory, not a file: ${filePath}`, "IS_DIRECTORY"); case "EMFILE": case "ENFILE": throw new errors_1.FileLoadError(`Too many open files. Please try again later.`, "TOO_MANY_FILES"); case "ENOSPC": throw new errors_1.FileLoadError(`No space left on device.`, "NO_SPACE"); default: throw new errors_1.FileLoadError(`Failed to load file "${filePath}": ${error.message}`, "FILE_LOAD_ERROR"); } } }); } /** * Check if source is a URL */ static isUrl(source) { try { const url = new URL(source); return url.protocol === "http:" || url.protocol === "https:"; } catch (_a) { return false; } } /** * Get file extension from source (URL or file path) */ static getFileExtension(source) { var _a; try { // Handle URLs - extract from pathname if (this.isUrl(source)) { const url = new URL(source); const pathname = url.pathname; // Handle query parameters that might contain file extensions const pathWithoutQuery = pathname.split("?")[0]; const extension = ((_a = pathWithoutQuery.split(".").pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || ""; return extension; } // Handle file paths const extension = path.extname(source).slice(1).toLowerCase(); // Remove the dot return extension; } catch (_b) { return ""; } } /** * Validate if the source appears to be a valid file path or URL */ static validateSource(source) { if (!source || typeof source !== "string" || source.trim().length === 0) { return { isValid: false, reason: "Source is empty or not a string" }; } const cleanSource = source.trim(); // Check if it's a URL if (this.isUrl(cleanSource)) { try { const url = new URL(cleanSource); if (url.protocol !== "http:" && url.protocol !== "https:") { return { isValid: false, reason: "Only HTTP and HTTPS URLs are supported", }; } return { isValid: true }; } catch (_a) { return { isValid: false, reason: "Invalid URL format" }; } } // Basic file path validation const invalidChars = /[<>:"|?*]/; if (invalidChars.test(cleanSource)) { return { isValid: false, reason: "File path contains invalid characters", }; } return { isValid: true }; } /** * Get content type from file extension */ static getContentType(extension) { const contentTypes = { geojson: "application/geo+json", json: "application/json", kml: "application/vnd.google-earth.kml+xml", kmz: "application/vnd.google-earth.kmz", xml: "application/xml", }; return contentTypes[extension.toLowerCase()] || "application/octet-stream"; } } exports.FileLoader = FileLoader;