# geo-toolkits

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

## 🌟 Features

- **🗺️ Multi-format Support**: Load GeoJSON, KML, and KMZ files
- **📁 Flexible Loading**: Support for both local files and remote URLs
- **🎯 Geofencing**: Point-in-polygon detection with hole support
- **📐 Area Calculations**: Accurate spherical area calculations for Earth surface
- **📊 Statistical Analysis**: Comprehensive area and geometric statistics
- **🔍 Spatial Queries**: Find closest features and multiple containment checks
- **⚡ Performance Optimized**: Bounding box pre-filtering for fast operations
- **📝 TypeScript**: Full type safety and IntelliSense support

## 📦 Installation

```bash
npm install geo-toolkits
```

## 🚀 Quick Start

```typescript
import { GeoToolKit } from 'geo-toolkits';

const kit = new GeoToolKit();

// Load spatial data
await kit.loadFromFile('./boundaries.geojson');
// or from URL
await kit.loadFromUrl('https://example.com/data.geojson');

// Check if a point is inside any polygon
const result = kit.contains(40.7128, -74.0060); // NYC coordinates
if (result.isInside) {
  console.log(`Point is inside feature: ${result.featureIndex}`);
}

// Calculate total area
const totalArea = kit.getArea(); // in square meters
const areaInKm2 = kit.getAreaInUnit('square_kilometers');

// Get bounding box
const bbox = kit.getBoundingBox();
console.log(`Area spans: ${bbox.maxLat - bbox.minLat} degrees latitude`);
```

## 📖 API Reference

### Data Loading

#### `loadFromFile(filePath: string): Promise<void>`
Load spatial data from a local file. Supports GeoJSON (.geojson, .json), KML (.kml), and KMZ (.kmz) formats.

```typescript
await kit.loadFromFile('./data/boundaries.geojson');
await kit.loadFromFile('./data/regions.kml');
await kit.loadFromFile('./data/areas.kmz');
```

#### `loadFromUrl(url: string): Promise<void>`
Load spatial data from a remote URL. Automatically detects file format and handles network timeouts.

```typescript
await kit.loadFromUrl('https://example.com/boundaries.geojson');
await kit.loadFromUrl('https://maps.example.com/regions.kml');
```

#### `isLoaded(): boolean`
Check if spatial data has been successfully loaded.

```typescript
if (kit.isLoaded()) {
  console.log('Data is ready for analysis');
}
```

### Dataset Information

#### `getDatasetInfo()`
Get comprehensive information about the loaded dataset.

```typescript
const info = kit.getDatasetInfo();
console.log({
  source: info.source,
  originalFormat: info.originalFormat, // 'geojson' | 'kml' | 'kmz'
  featureCount: info.featureCount,
  polygonCount: info.polygonCount,
  multiPolygonCount: info.multiPolygonCount,
  fileSize: info.fileSize,
  loadedAt: info.loadedAt
});
```

### Feature Access

#### `getFeatures(): SpatialFeature[]`
Get all loaded spatial features (Polygons and MultiPolygons only).

```typescript
const features = kit.getFeatures();
features.forEach((feature, index) => {
  console.log(`Feature ${index}: ${feature.type}`);
  console.log(`Properties:`, feature.properties);
});
```

#### `getPolygons(): PolygonFeature[]`
Get only Polygon features from the dataset.

```typescript
const polygons = kit.getPolygons();
console.log(`Found ${polygons.length} polygon features`);
```

#### `getMultiPolygons(): MultiPolygonFeature[]`
Get only MultiPolygon features from the dataset.

```typescript
const multiPolygons = kit.getMultiPolygons();
console.log(`Found ${multiPolygons.length} multi-polygon features`);
```

### Geofencing Operations

#### `contains(lat: number, lng: number): ContainmentResult`
Check if a coordinate point is contained within any polygon feature. Handles polygons with holes and MultiPolygon features.

```typescript
const result = kit.contains(40.7128, -74.0060);

if (result.isInside) {
  console.log(`Point is inside feature ${result.featureIndex}`);
  console.log(`Feature properties:`, result.properties);
  
  if (result.geometryType === 'MultiPolygon') {
    console.log(`Specific polygon: ${result.polygonIndex}`);
  }
}
```

#### `findClosestFeature(lat: number, lng: number)`
Find the closest feature to a given coordinate point.

```typescript
const closest = kit.findClosestFeature(40.7128, -74.0060);
if (closest) {
  console.log(`Closest feature: ${closest.featureIndex}`);
  console.log(`Distance: ${closest.distance} meters`);
}
```

#### `getAllContainingFeatures(lat: number, lng: number): ContainmentResult[]`
Get all features that contain the given coordinate (useful for overlapping polygons).

```typescript
const allContaining = kit.getAllContainingFeatures(40.7128, -74.0060);
console.log(`Point is contained in ${allContaining.length} features`);

allContaining.forEach(result => {
  console.log(`Feature ${result.featureIndex}: ${result.properties.name}`);
});
```

### Geometry Calculations

#### `getBoundingBox(): BoundingBox | null`
Get the overall bounding box that encompasses all features.

```typescript
const bbox = kit.getBoundingBox();
if (bbox) {
  console.log(`Latitude range: ${bbox.minLat} to ${bbox.maxLat}`);
  console.log(`Longitude range: ${bbox.minLng} to ${bbox.maxLng}`);
}
```

#### `getMidpoint(): MidpointResult | null`
Calculate the geometric center (midpoint) of all features.

```typescript
const center = kit.getMidpoint();
if (center) {
  console.log(`Dataset center: ${center.latitude}, ${center.longitude}`);
}
```

#### `isWithinBoundingBox(lat: number, lng: number): boolean`
Quick check if a coordinate is within the overall dataset bounding box.

```typescript
const withinBounds = kit.isWithinBoundingBox(40.7128, -74.0060);
console.log(`Point within dataset bounds: ${withinBounds}`);
```

### Area Calculations

#### `getArea(): number`
Calculate the total area of all features in square meters using spherical geometry.

```typescript
const totalArea = kit.getArea(); // square meters
console.log(`Total area: ${totalArea.toLocaleString()} m²`);
```

#### `getAreaInUnit(unit): number`
Get total area in specific units.

```typescript
const areaKm2 = kit.getAreaInUnit('square_kilometers');
const areaHectares = kit.getAreaInUnit('hectares');
const areaAcres = kit.getAreaInUnit('acres');

console.log(`Area: ${areaKm2.toFixed(2)} km²`);
console.log(`Area: ${areaHectares.toFixed(2)} hectares`);
console.log(`Area: ${areaAcres.toFixed(2)} acres`);
```

#### `getAreaDetails(): AreaResult`
Get detailed area breakdown for each feature.

```typescript
const details = kit.getAreaDetails();
console.log(`Total area: ${details.totalArea} ${details.unit}`);

details.features.forEach(feature => {
  console.log(`Feature ${feature.featureIndex}: ${feature.area} m²`);
  
  // For MultiPolygon features
  if (feature.polygonAreas) {
    feature.polygonAreas.forEach((polyArea, index) => {
      console.log(`  Polygon ${index}: ${polyArea} m²`);
    });
  }
});
```

#### `getFeatureArea(featureIndex: number): number`
Calculate area of a specific feature by index.

```typescript
const feature0Area = kit.getFeatureArea(0);
console.log(`Feature 0 area: ${feature0Area} m²`);
```

#### `getAreaStatistics()`
Get comprehensive area statistics for the dataset.

```typescript
const stats = kit.getAreaStatistics();
console.log({
  totalArea: stats.totalArea,
  averageArea: stats.averageArea,
  largestFeature: stats.largestFeature,
  smallestFeature: stats.smallestFeature,
  distribution: {
    min: stats.areaDistribution.min,
    max: stats.areaDistribution.max,
    median: stats.areaDistribution.median,
    standardDeviation: stats.areaDistribution.stdDev
  }
});
```

### Utility Methods

#### `validateCoordinates(lat: number, lng: number): boolean`
Validate if coordinates are within valid ranges.

```typescript
const isValid = kit.validateCoordinates(40.7128, -74.0060);
if (!isValid) {
  console.log('Invalid coordinates provided');
}
```

## 🎯 Use Cases

### Real Estate Applications
```typescript
// Load property boundaries
await kit.loadFromFile('./property-boundaries.geojson');

// Check if a house location is within a specific zone
const houseLocation = kit.contains(40.7128, -74.0060);
if (houseLocation.isInside) {
  const zone = houseLocation.properties.zoning_type;
  console.log(`House is in ${zone} zone`);
}

// Calculate total developable area
const totalArea = kit.getAreaInUnit('acres');
console.log(`Total developable area: ${totalArea} acres`);
```

### Delivery & Logistics
```typescript
// Load delivery zones
await kit.loadFromUrl('https://api.company.com/delivery-zones.geojson');

// Check if delivery address is serviceable
const deliveryLocation = kit.contains(customerLat, customerLng);
if (deliveryLocation.isInside) {
  const zone = deliveryLocation.properties.delivery_zone;
  const fee = deliveryLocation.properties.delivery_fee;
  console.log(`Delivery available in ${zone} for $${fee}`);
} else {
  console.log('Sorry, we don\'t deliver to this location');
}
```

### Environmental Monitoring
```typescript
// Load protected areas
await kit.loadFromFile('./protected-areas.geojson');

// Check if coordinates are in protected zone
const location = kit.contains(sensorLat, sensorLng);
if (location.isInside) {
  const protectionLevel = location.properties.protection_level;
  console.log(`Sensor is in ${protectionLevel} protected area`);
}

// Calculate total protected area
const protectedArea = kit.getAreaInUnit('hectares');
console.log(`Total protected area: ${protectedArea} hectares`);
```

### Urban Planning
```typescript
// Load city districts
await kit.loadFromFile('./city-districts.geojson');

// Analyze district sizes
const areaStats = kit.getAreaStatistics();
console.log(`Largest district: ${areaStats.largestFeature.area / 1000000} km²`);
console.log(`Average district size: ${areaStats.averageArea / 1000000} km²`);

// Find which district a new development is in
const development = kit.contains(newDevLat, newDevLng);
if (development.isInside) {
  const district = development.properties.district_name;
  console.log(`New development is in ${district}`);
}
```

## 🔧 Advanced Usage

### Working with Multiple Datasets
```typescript
const residentialKit = new GeoToolKit();
const commercialKit = new GeoToolKit();

await residentialKit.loadFromFile('./residential-zones.geojson');
await commercialKit.loadFromFile('./commercial-zones.geojson');

// Check property zoning
const residentialCheck = residentialKit.contains(lat, lng);
const commercialCheck = commercialKit.contains(lat, lng);

if (residentialCheck.isInside) {
  console.log('Property is in residential zone');
} else if (commercialCheck.isInside) {
  console.log('Property is in commercial zone');
}
```

### Error Handling
```typescript
try {
  await kit.loadFromUrl('https://example.com/data.geojson');
  
  const result = kit.contains(lat, lng);
  console.log('Geofencing check completed');
  
} catch (error) {
  if (error.code === 'FILE_NOT_FOUND') {
    console.log('Data file not found');
  } else if (error.code === 'INVALID_COORDINATES') {
    console.log('Invalid coordinates provided');
  } else {
    console.log('An error occurred:', error.message);
  }
}
```

## 📊 Supported File Formats

| Format | Extension | Description |
|--------|-----------|-------------|
| GeoJSON | `.geojson`, `.json` | Standard geospatial JSON format |
| KML | `.kml` | Keyhole Markup Language |
| KMZ | `.kmz` | Compressed KML format |

**Note**: Only Polygon and MultiPolygon geometries are supported. Point, LineString, and other geometry types are filtered out during loading.

## 🌍 Coordinate System

- **Input**: WGS84 (EPSG:4326) latitude/longitude coordinates
- **Range**: Latitude: -90 to 90, Longitude: -180 to 180
- **Area Calculations**: Uses spherical geometry for accurate Earth surface area calculations
- **Distance Calculations**: Uses Haversine formula for great circle distances

## ⚡ Performance

- **Bounding Box Optimization**: Fast pre-filtering eliminates ~90% of expensive polygon checks
- **Spatial Indexing**: Efficient point-in-polygon operations
- **Memory Efficient**: Processes large datasets without excessive memory usage
- **TypeScript**: Zero runtime overhead with full compile-time type checking

## 🔗 TypeScript Support

Full TypeScript support with comprehensive type definitions:

```typescript
import { 
  GeoToolKit, 
  type ContainmentResult, 
  type BoundingBox,
  type AreaResult 
} from 'geo-toolkits';

const kit = new GeoToolKit();
const result: ContainmentResult = kit.contains(40.7128, -74.0060);
const bbox: BoundingBox | null = kit.getBoundingBox();
```

## 📄 License

MIT

## 🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## 📞 Support

For issues and questions, please visit our [GitHub repository](https://github.com/bolu1/geo-toolkits).