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.
407 lines (406 loc) • 16.2 kB
JavaScript
"use strict";
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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ErrorUtils = exports.ErrorHandler = exports.ERROR_CODES = exports.ConfigurationError = exports.GeometryError = exports.ValidationError = exports.ParseError = exports.FileLoadError = exports.GeoToolkitsError = void 0;
class GeoToolkitsError extends Error {
constructor(message, code = 'UNKNOWN_ERROR', context) {
super(message);
this.name = 'GeoToolkitsError';
this.code = code;
this.timestamp = new Date().toISOString();
this.context = context;
// Maintain proper stack trace for debugging
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
/**
* Get formatted error information
*/
getErrorInfo() {
return {
name: this.name,
code: this.code,
message: this.message,
timestamp: this.timestamp,
context: this.context,
stack: this.stack
};
}
/**
* Get user-friendly error message
*/
getUserMessage() {
return this.message;
}
/**
* Convert error to JSON
*/
toJSON() {
return {
name: this.name,
code: this.code,
message: this.message,
timestamp: this.timestamp,
context: this.context
};
}
}
exports.GeoToolkitsError = GeoToolkitsError;
/**
* File loading and I/O related errors
*/
class FileLoadError extends GeoToolkitsError {
constructor(message, code = 'FILE_LOAD_ERROR', context) {
super(message, code, context);
this.name = 'FileLoadError';
}
getUserMessage() {
switch (this.code) {
case 'FILE_NOT_FOUND':
return 'The specified file could not be found. Please check the file path and try again.';
case 'PERMISSION_DENIED':
return 'Permission denied. Please check that you have read access to the file.';
case 'URL_FETCH_ERROR':
return 'Failed to download the file from the URL. Please check your internet connection and the URL.';
case 'TIMEOUT_ERROR':
return 'The request timed out. Please try again or use a longer timeout.';
case 'UNSUPPORTED_PROTOCOL':
return 'Only HTTP and HTTPS URLs are supported.';
case 'DNS_ERROR':
return 'Could not resolve the domain name. Please check the URL and your internet connection.';
case 'CONNECTION_REFUSED':
return 'Connection was refused by the server. Please check if the server is running.';
case 'EMPTY_FILE':
return 'The file is empty. Please provide a file with valid content.';
case 'TOO_MANY_FILES':
return 'Too many files are open. Please try again in a moment.';
case 'NO_SPACE':
return 'Not enough disk space available.';
default:
return this.message;
}
}
}
exports.FileLoadError = FileLoadError;
/**
* File parsing and format related errors
*/
class ParseError extends GeoToolkitsError {
constructor(message, code = 'PARSE_ERROR', context) {
super(message, code, context);
this.name = 'ParseError';
this.originalData = context === null || context === void 0 ? void 0 : context.originalData;
}
getUserMessage() {
switch (this.code) {
case 'INVALID_GEOJSON':
return 'The file does not contain valid GeoJSON data. Please check the file format.';
case 'INVALID_KML':
return 'The file does not contain valid KML data. Please check the file format.';
case 'INVALID_KMZ':
return 'The KMZ file is corrupted or invalid. Please try with a different file.';
case 'UNSUPPORTED_FORMAT':
return 'The file format is not supported. Please use GeoJSON (.geojson), KML (.kml), or KMZ (.kmz) files.';
case 'INVALID_JSON':
return 'The file contains invalid JSON syntax. Please check the file content.';
case 'INVALID_XML':
return 'The file contains invalid XML syntax. Please check the file content.';
case 'NO_KML_IN_KMZ':
return 'No KML file found inside the KMZ archive. Please ensure the KMZ file contains a valid KML file.';
case 'INVALID_COORDINATES':
return 'The file contains invalid coordinate data. Coordinates must be valid latitude/longitude values.';
case 'INVALID_GEOMETRY':
return 'The file contains invalid geometry data. Please check the coordinate structure.';
case 'NO_FEATURES_FOUND':
return 'No valid polygon or multipolygon features found in the file.';
default:
return this.message;
}
}
}
exports.ParseError = ParseError;
/**
* Data validation and input errors
*/
class ValidationError extends GeoToolkitsError {
constructor(message, code = 'VALIDATION_ERROR', context) {
super(message, code, context);
this.name = 'ValidationError';
this.invalidValue = context === null || context === void 0 ? void 0 : context.invalidValue;
}
getUserMessage() {
switch (this.code) {
case 'INVALID_COORDINATES':
return 'Invalid coordinates provided. Latitude must be between -90 and 90, longitude between -180 and 180.';
case 'NOT_LOADED':
return 'No spatial data has been loaded. Please load a file first using loadFromUrl() or loadFromFile().';
case 'NO_FEATURES_FOUND':
return 'No valid features found in the loaded data.';
case 'INVALID_SOURCE':
return 'Invalid file path or URL provided. Please check the source and try again.';
case 'EMPTY_DATASET':
return 'The dataset is empty or contains no valid spatial features.';
case 'INVALID_FEATURE_INDEX':
return 'Invalid feature index. Please provide a valid feature index within the dataset range.';
default:
return this.message;
}
}
}
exports.ValidationError = ValidationError;
/**
* Geometric calculation and spatial analysis errors
*/
class GeometryError extends GeoToolkitsError {
constructor(message, code = 'GEOMETRY_ERROR', context) {
super(message, code, context);
this.name = 'GeometryError';
}
getUserMessage() {
switch (this.code) {
case 'EMPTY_GEOMETRY':
return 'The geometry is empty or contains no coordinates.';
case 'INVALID_POLYGON':
return 'Invalid polygon geometry. Polygons must have at least 3 coordinate pairs.';
case 'SELF_INTERSECTING_POLYGON':
return 'The polygon intersects with itself, which may cause calculation errors.';
case 'CALCULATION_ERROR':
return 'An error occurred during geometric calculations. Please check your data.';
case 'UNSUPPORTED_GEOMETRY':
return 'This geometry type is not supported for the requested operation.';
case 'INVALID_BOUNDING_BOX':
return 'Invalid bounding box coordinates provided.';
default:
return this.message;
}
}
}
exports.GeometryError = GeometryError;
/**
* Configuration and setup errors
*/
class ConfigurationError extends GeoToolkitsError {
constructor(message, code = 'CONFIGURATION_ERROR', context) {
super(message, code, context);
this.name = 'ConfigurationError';
}
getUserMessage() {
switch (this.code) {
case 'INVALID_OPTIONS':
return 'Invalid configuration options provided.';
case 'MISSING_DEPENDENCY':
return 'A required dependency is missing. Please check your installation.';
case 'UNSUPPORTED_ENVIRONMENT':
return 'This feature is not supported in the current environment.';
default:
return this.message;
}
}
}
exports.ConfigurationError = ConfigurationError;
// Error codes enumeration for reference and type safety
exports.ERROR_CODES = {
// File loading errors
FILE_NOT_FOUND: 'FILE_NOT_FOUND',
PERMISSION_DENIED: 'PERMISSION_DENIED',
URL_FETCH_ERROR: 'URL_FETCH_ERROR',
TIMEOUT_ERROR: 'TIMEOUT_ERROR',
CONNECTION_REFUSED: 'CONNECTION_REFUSED',
CONNECTION_RESET: 'CONNECTION_RESET',
DNS_ERROR: 'DNS_ERROR',
HTTP_ERROR: 'HTTP_ERROR',
UNSUPPORTED_PROTOCOL: 'UNSUPPORTED_PROTOCOL',
EMPTY_FILE: 'EMPTY_FILE',
NOT_A_FILE: 'NOT_A_FILE',
IS_DIRECTORY: 'IS_DIRECTORY',
TOO_MANY_FILES: 'TOO_MANY_FILES',
NO_SPACE: 'NO_SPACE',
// Format detection and parsing errors
UNSUPPORTED_FORMAT: 'UNSUPPORTED_FORMAT',
INVALID_GEOJSON: 'INVALID_GEOJSON',
INVALID_GEOJSON_STRUCTURE: 'INVALID_GEOJSON_STRUCTURE',
INVALID_GEOJSON_TYPE: 'INVALID_GEOJSON_TYPE',
INVALID_FEATURES_ARRAY: 'INVALID_FEATURES_ARRAY',
INVALID_JSON: 'INVALID_JSON',
INVALID_XML: 'INVALID_XML',
INVALID_KML: 'INVALID_KML',
INVALID_KMZ: 'INVALID_KMZ',
NO_KML_IN_KMZ: 'NO_KML_IN_KMZ',
// Validation errors
INVALID_COORDINATES: 'INVALID_COORDINATES',
INVALID_GEOMETRY: 'INVALID_GEOMETRY',
NO_FEATURES_FOUND: 'NO_FEATURES_FOUND',
NO_VALID_FEATURES: 'NO_VALID_FEATURES',
NOT_LOADED: 'NOT_LOADED',
INVALID_SOURCE: 'INVALID_SOURCE',
EMPTY_DATASET: 'EMPTY_DATASET',
INVALID_FEATURE_INDEX: 'INVALID_FEATURE_INDEX',
// Geometry errors
EMPTY_GEOMETRY: 'EMPTY_GEOMETRY',
INVALID_POLYGON: 'INVALID_POLYGON',
SELF_INTERSECTING_POLYGON: 'SELF_INTERSECTING_POLYGON',
CALCULATION_ERROR: 'CALCULATION_ERROR',
UNSUPPORTED_GEOMETRY: 'UNSUPPORTED_GEOMETRY',
INVALID_BOUNDING_BOX: 'INVALID_BOUNDING_BOX',
// Configuration errors
INVALID_OPTIONS: 'INVALID_OPTIONS',
MISSING_DEPENDENCY: 'MISSING_DEPENDENCY',
UNSUPPORTED_ENVIRONMENT: 'UNSUPPORTED_ENVIRONMENT',
// General errors
UNKNOWN_ERROR: 'UNKNOWN_ERROR'
};
/**
* Error handler utility class
*/
class ErrorHandler {
/**
* Check if error is a geo-toolkits error
*/
static isGeoToolkitsError(error) {
return error instanceof GeoToolkitsError;
}
/**
* Get user-friendly error message from any error
*/
static getUserMessage(error) {
if (this.isGeoToolkitsError(error)) {
return error.getUserMessage();
}
// Handle common Node.js errors
if (error.code) {
switch (error.code) {
case 'ENOENT':
return 'File not found. Please check the file path.';
case 'EACCES':
return 'Permission denied. Please check file permissions.';
case 'ENOTFOUND':
return 'Network error. Please check your internet connection.';
case 'ECONNREFUSED':
return 'Connection refused. The server may be down.';
case 'ETIMEDOUT':
return 'Request timed out. Please try again.';
}
}
return error.message || 'An unexpected error occurred.';
}
/**
* Create a standardized error response
*/
static createErrorResponse(error) {
const isGeoError = this.isGeoToolkitsError(error);
return {
success: false,
error: {
message: this.getUserMessage(error),
code: isGeoError ? error.code : 'UNKNOWN_ERROR',
type: isGeoError ? error.name : 'UnknownError',
timestamp: isGeoError ? error.timestamp : new Date().toISOString(),
context: isGeoError ? error.context : undefined
}
};
}
/**
* Log error with appropriate level
*/
static logError(error, context) {
const errorInfo = this.isGeoToolkitsError(error) ? error.getErrorInfo() : {
name: error.constructor.name,
message: error.message,
stack: error.stack
};
const logMessage = context
? `[${context}] ${errorInfo.name}: ${errorInfo.message}`
: `${errorInfo.name}: ${errorInfo.message}`;
// Log based on error severity
if (this.isGeoToolkitsError(error)) {
switch (error.constructor.name) {
case 'ValidationError':
console.warn('⚠️', logMessage);
break;
case 'FileLoadError':
case 'ParseError':
console.error('❌', logMessage);
break;
case 'GeometryError':
console.error('🔺', logMessage);
break;
default:
console.error('💥', logMessage);
}
}
else {
console.error('💥', logMessage);
}
}
/**
* Wrap async function with error handling
*/
static wrapAsync(fn, context) {
return __awaiter(this, void 0, void 0, function* () {
try {
return yield fn();
}
catch (error) {
this.logError(error, context);
throw error;
}
});
}
/**
* Create error from validation result
*/
static createValidationError(field, value, requirement) {
return new ValidationError(`Invalid ${field}: ${requirement}`, 'INVALID_FIELD', { field, value, requirement });
}
}
exports.ErrorHandler = ErrorHandler;
/**
* Utility functions for error handling
*/
exports.ErrorUtils = {
/**
* Check if value is a valid coordinate
*/
validateCoordinate(lat, lng) {
if (typeof lat !== 'number' || typeof lng !== 'number') {
throw new ValidationError('Coordinates must be numbers', 'INVALID_COORDINATES', { lat, lng });
}
if (isNaN(lat) || isNaN(lng)) {
throw new ValidationError('Coordinates cannot be NaN', 'INVALID_COORDINATES', { lat, lng });
}
if (lat < -90 || lat > 90) {
throw new ValidationError('Latitude must be between -90 and 90 degrees', 'INVALID_COORDINATES', { lat, lng });
}
if (lng < -180 || lng > 180) {
throw new ValidationError('Longitude must be between -180 and 180 degrees', 'INVALID_COORDINATES', { lat, lng });
}
},
/**
* Check if source is valid
*/
validateSource(source) {
if (!source || typeof source !== 'string' || source.trim().length === 0) {
throw new ValidationError('Source must be a non-empty string', 'INVALID_SOURCE', { source });
}
},
/**
* Check if feature index is valid
*/
validateFeatureIndex(index, maxIndex) {
if (typeof index !== 'number' || isNaN(index)) {
throw new ValidationError('Feature index must be a number', 'INVALID_FEATURE_INDEX', { index, maxIndex });
}
if (index < 0 || index >= maxIndex) {
throw new ValidationError(`Feature index must be between 0 and ${maxIndex - 1}`, 'INVALID_FEATURE_INDEX', { index, maxIndex });
}
}
};