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
JavaScript
;
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;