Home
Softono
fastgeotoolkit

fastgeotoolkit

Open source MIT Rust
61
Stars
4
Forks
2
Issues
15
Watchers
1 month
Last Commit

About fastgeotoolkit

Performant JS library for GPS route density mapping

Platforms

Web Self-hosted

Languages

Rust

fastgeotoolkit npm Docs Demo

Rust Tests JavaScript Tests CodeQL License

fastgeotoolkit is a library for GPS data processing and route density mapping. The core of the library is written in Rust and it's compiled to webassembly for use in the browser and node.

[!NOTE] Only Javascript/Typescript is supported at the moment. Rust and Python releases are planned.

What it does

The main use case is creating route heatmaps where you want to see which paths/routes are used most frequently. You can test this functionality at https://fastgeotoolkit-demo.pages.dev/, using either your own data or sample data. This is an example of what a heatmap produced using fastgeotoolkit looks like: https://i.ibb.co/MxpHbVdp/image.png

However, beyond this primary usecase, this library helps you:

  • Analyze GPS tracks (distance, statistics, intersections)
  • Decode Google polylines
  • Convert between GPS data formats

Documentation

Docs are available at https://fastgeotoolkit.pages.dev/.

Installation

npm install fastgeotoolkit
# or 
pnpm i fastgeotoolkit

Basic Usage

import { processGpxFiles } from 'fastgeotoolkit';

// Process GPX files into a heatmap
const gpxFile1 = new Uint8Array(/* your GPX file data */);
const gpxFile2 = new Uint8Array(/* another GPX file */);

const result = await processGpxFiles([gpxFile1, gpxFile2]);

// Result contains tracks with frequency data
console.log(`Found ${result.tracks.length} unique track segments`);
console.log(`Maximum frequency: ${result.max_frequency}`);

result.tracks.forEach(track => {
  console.log(`Track with ${track.coordinates.length} points, used ${track.frequency} times`);
});

Working with Polylines

import { decodePolyline, processPolylines } from 'fastgeotoolkit';

// Decode a single polyline
const coords = await decodePolyline('_p~iF~ps|U_ulLnnqC_mqNvxq`@');
console.log(coords); // [[lat, lng], [lat, lng], ...]

// Process multiple polylines into a heatmap
const polylines = [
  '_p~iF~ps|U_ulLnnqC_mqNvxq`@',
  'another_encoded_polyline',
  'yet_another_one'
];
const heatmap = await processPolylines(polylines);

Track Analysis

import { calculateTrackStatistics, validateCoordinates } from 'fastgeotoolkit';

const coordinates = [[37.7749, -122.4194], [37.7849, -122.4094]]; // [lat, lng] pairs

// Get basic statistics
const stats = await calculateTrackStatistics(coordinates);
console.log(`Distance: ${stats.distance_km.toFixed(2)} km`);
console.log(`${stats.point_count} GPS points`);
console.log(`Bounds: ${stats.bounding_box}`); // [min_lat, min_lng, max_lat, max_lng]

// Validate coordinates
const validation = await validateCoordinates(coordinates);
console.log(`${validation.valid_count} out of ${validation.total_count} coordinates are valid`);
if (validation.issues.length > 0) {
  console.log('Issues found:', validation.issues);
}

Data Conversion

import { coordinatesToGeojson, exportToGpx } from 'fastgeotoolkit';

// Convert to GeoJSON
const geojson = await coordinatesToGeojson(coordinates, {
  name: 'My Route',
  activity: 'cycling'
});

// Export multiple tracks as GPX
const tracks = [track1_coordinates, track2_coordinates];
const gpxString = await exportToGpx(tracks, {
  creator: 'My App',
  name: 'Route Collection'
});

Real-world Example

Here's an example of how you might use this in a web app to show route popularity:

import { processGpxFiles } from 'fastgeotoolkit';

async function createHeatmap(gpxFiles) {
  // Convert files to Uint8Array
  const fileBuffers = await Promise.all(
    gpxFiles.map(file => file.arrayBuffer().then(buf => new Uint8Array(buf)))
  );

  // Process into heatmap
  const heatmap = await processGpxFiles(fileBuffers);

  // Render on map (example with any mapping library)
  heatmap.tracks.forEach(track => {
    const intensity = track.frequency / heatmap.max_frequency;
    const color = `hsl(${(1-intensity) * 240}, 100%, 50%)`; // blue to red

    drawLineOnMap(track.coordinates, {
      color: color,
      weight: Math.max(2, intensity * 8)
    });
  });
}

// Usage
document.getElementById('file-input').addEventListener('change', async (e) => {
  const files = Array.from(e.target.files);
  await createHeatmap(files);
});

TypeScript Support

The library includes full TypeScript definitions:

import type { 
  Coordinate,        // [number, number] - [lat, lng]
  HeatmapResult,     // { tracks: HeatmapTrack[], max_frequency: number }
  HeatmapTrack,      // { coordinates: Coordinate[], frequency: number }
  TrackStatistics,   // distance, bounds, point count, etc.
  ValidationResult,  // validation results with issues
  FileInfo          // file format information
} from 'fastgeotoolkit';

JavaScript Utilities

For simple operations that don't rely on WebAssembly:

import { utils } from 'fastgeotoolkit';

// Basic coordinate validation
if (utils.isValidCoordinate(37.7749, -122.4194)) {
  console.log('Valid GPS coordinate');
}

// Calculate distance between two points
const distance = utils.haversineDistance(37.7749, -122.4194, 37.7849, -122.4094);
console.log(`Distance: ${distance.toFixed(2)} km`);

// Get bounding box
const bounds = utils.getBoundingBox(coordinates);
console.log(`Bounds: ${bounds}`); // [min_lat, min_lng, max_lat, max_lng]

Browser vs Node.js

Works the same in both environments:

// Browser
import { processGpxFiles } from 'fastgeotoolkit';

// Node.js  
const { processGpxFiles } = require('fastgeotoolkit');
// or with ES modules:
import { processGpxFiles } from 'fastgeotoolkit';

Performance Notes

  • WebAssembly provides near-native performance for GPS processing
  • Large datasets (thousands of tracks) process quickly
  • First function call initializes WebAssembly (adds ~100ms startup time)

Common Issues

"Cannot resolve module" errors: Make sure your bundler supports WebAssembly. Modern bundlers (Vite, Webpack 5+, etc.) work out of the box.

TypeScript errors: Ensure you're using TypeScript 4.0+ for proper WebAssembly typing support.

File reading: Remember to convert File objects to Uint8Array:

const buffer = await file.arrayBuffer();
const uint8Array = new Uint8Array(buffer);

Development & Maintenance

This project consists of Rust code compiled to WebAssembly with JavaScript/TypeScript bindings.

Project Structure

  • /src/ - Rust source code
  • /dist/javascript/ - JavaScript/TypeScript bindings and NPM package
  • /dist/wasm/ - Generated WebAssembly files
  • /demo/ - Demo application (SvelteKit)
  • /docs/ - Generated documentation

    [!NOTE] /dist/python and /dist/rust/ contain WIP releases for their respective ecosystems, but they're not in working order yet.

Compiling Rust to WebAssembly

To compile the Rust code to WebAssembly:

# Install wasm-pack if you haven't already
cargo install wasm-pack

# Build the WebAssembly module
wasm-pack build --target web --out-dir dist/wasm

Building the NPM Package

To build the complete NPM package with all bindings:

# From the root directory
npm run build

# Or build individual components:
npm run build:wasm    # Build WebAssembly
npm run build:js      # Build JavaScript bindings
npm run build:docs    # Build documentation

Building Documentation

The documentation is generated using TypeDoc and can be built locally:

cd dist/javascript
npm run docs

This will generate the documentation website in the docs/ directory.

Testing

# Run Rust tests
cargo test

# Run JavaScript tests
cd dist/javascript
npm test

License

MIT