# Route Point Check

[![npm version](https://img.shields.io/npm/v/route-point-check.svg)](https://www.npmjs.com/package/route-point-check)
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![node](https://img.shields.io/badge/node-%3E=18-brightgreen.svg)](https://nodejs.org)
[![tests](https://github.com/berkcansavur/route-point-check/actions/workflows/ci.yml/badge.svg)](https://github.com/berkcansavur/route-point-check/actions/workflows/ci.yml)

TypeScript library for proximity and coverage checks between route polylines and points. Production-focused: safe, fast, readable.

## 🚀 Features

- **Polyline Decode**: Decodes Google encoded polyline strings
- **Shortest Distance Algorithm**: Computes shortest distance from points to polyline
- **Dynamic Radius Algorithm**: Computes dynamic radius around route points
- **Visualization**: Generates comparison HTML for both algorithms
- **Tabbed Interface**: Compare algorithms side-by-side
- **TypeScript Support**: Full typing with strictNullChecks

## 📦 Install

```bash
npm install route-point-check
```

## 🔧 Usage

### Basic usage

```typescript
import {
  getPointsOnRoute,
  getPolylineString,
  getDistance,
  AlgorithmName,
  type LatLng,
  type H3Index
} from 'route-point-check';

// Polyline as a value object
const polylineString = getPolylineString(
  "oh|yFyucpDMaDAk@AYb@L^Tv@j@j@x@l@xAVhAhBdOd@vHh@bFj@xE~@hErBxGf@hArBdEbE~Eh@b@"
  + "dHnEzE|ClAfAl@d@@L@JpAzAh@h@hBxBZp@N~ADp@KpByBlNsBrMWbC"
  + "{AjUGfIHfFJ`CN`@NdFRjEl@xI`B~PtAvR\\lGJvMPdHj@nLd@`ML~H?"
  + "hMKpZHfJXpOhA~Rp@vE~C`NtBpElCvElEpF~GzHpAjBdEhIzArEtAhGv@lG`@vFJnEArEg@tJkA"
  + "~Im@nDwCbPoCrNo@rDq@lG[dFQtHFpMb@tIr@lIbAfH|BhKpEjP|BtLfAbJp@dI`@pL?"
  + "xHWnMg@bNS~NLfPFrBt@vMv@nHnCfQjAhFhDpLhCfH~BxFfFzJzDdGlClDlJ`KpC~BtAdAB^`Ar"
  + "@|ArAdEhChNrHlHfEvC~BrChC|F|"
  + "FhCxCpDdFlDxFxDzHjBrFHhBKx@u@hBgAp@u@EeB_Ay@g@k@Qe@Eo@F_Ab@i@r@c@nAgAnGUrCD"
  + "bCThH\\lHn@`Cl@fA`AbAhHzF~EnF`NbPrAxBr@`BZ|@t@|Cb@CNnC?~C"
  + "[pEg@tDcAtD_BdDiEtHaAjBsAdD}@jDoBjIcDnMk@fCu@bFIdAKzCGjFF~DH|"
  + "DQ`IUbEOdBiA~I_AxEqB|"
  + "Ia@rAw@pAs@f@eA\\EPcB@mAXeAp@aBvAcB`AqD`BQT_DfAyE`ByFdBaEl@eETcDBaB?cE_@}"
  + "LwAwHc@oJ?cMb@OXiKHoCEMMUKc@E@AeBO}Bi@sAs@wAgAsBuBqAuAoAaAi@OeAKu@DaAXUJ]"
  + "TmAtAWb@i@~AO~A?vAv@zJfAfMFrCBvGQ`KKjDUxA_@vA}@xBgBxBkCzB}ArA}"
  + "@rAeAjBoAzCgAlEa@|CgAdLU|@IROVCh@HP@|Ay@bF{D\\uBPoA@}ACM?q@b@kNpJg@DO?"
  + "KHCVFTLDz@pFpEbWb@zBj@tBpCtNj@~Cf@fBb@j@j@X`A?dAOb@?DnAoD|C}"
  + "Az@sDrAgClAyAr@o@h@eB|Ag@d@{A`@mANm@JuD|"
  + "@WRu@vAu@~Cq@pAkAlAgAf@G@IFCPDNBBbCfIz@zCCDELNVT?zHtGxBdBd@hAXhAVdBXjAf@`@@"
  + "[z@C`F?bEG~ECdFDx@KtAYtAOx@El@Fp@VFFTFx@vApC|FhAbC~BdFnCbGXl@?XALIb@uBjB"
  + "{@t@gCzBiKfIeCrAK@IXBJlCrLpFbJf@`A?FBJFFhCpKE~@yAlBoBdCM`ALx@r@h@t@@DZa@|"
  + "@??qBrCW\\K^G@?^Lh@t@vBZvABn@QdB@h@Hh@x@vBbDvIHd@Ah@MhAKr@g@vCeAtDi@`BMj@?"
  + "jA@XGZWn@Sf@J|@\\G`CUxCWfAGhGm@|AU`A[~@[jAi@n@MN?"
  + "RBx@ZjAd@h@NZB`@CjB@dBKvAO~A_@fA[jBq@b@If@Ct@?ZBbB^d@L`ATHHd@PZLt@b@|"
  + "AdA`BxAh@b@d@Vx@TbARvAR^IjAs@|B_CpEcF`AoATe@Tw@Z_BLwAN}"
  + "@Ne@RUb@YXMdDLFyBJuCDuAHuA^iFNaDHmCLyLFmK@qBnCD~CDB"
  + "{B@cDDaEGsBCcAJ@l@@@@p@Ch@@pAFxA@hDHrFJtA?n@@@BrAApAHfDDrAJrADpAFjAL?d@"
);

// Points can be LatLng[] or H3Index[]
const points: Array<LatLng | H3Index> = [
  { lat: 41.0671, lng: 29.0167 },
  { lat: 41.0665, lng: 29.0172 },
  { lat: 41.0659, lng: 29.0178 }
];

const tolerance = getDistance(100, 'meters');

// Shortest distance
const shortest = getPointsOnRoute(polylineString, points, tolerance, AlgorithmName.SHORTEST_DISTANCE);

// Radius-based
const radius = getPointsOnRoute(polylineString, points, tolerance, AlgorithmName.RADIUS);

console.log(shortest.summary.matchedCount.toNumber());
console.log(radius.summary.matchedCount.toNumber());
```

### Local linking for development

1. In this repo: `npm link`
2. In your consumer project: `npm link route-point-check`
3. Test import:
```ts
// ESM
import { ShortestDistanceAlgorithm } from 'route-point-check'

// or CJS
const { ShortestDistanceAlgorithm } = require('route-point-check')
```

### Visualization

```typescript
import { generateVisuals, getPolylineString, getDistance } from 'route-point-check';
import { Environment } from 'route-point-check/dist/types';
import type { LatLng, H3Index } from 'route-point-check';

const polyline = getPolylineString('...');
const points: Array<LatLng | H3Index> = [ { lat: 41.0671, lng: 29.0167 } ];

const result = await generateVisuals({
  polyline,
  points,
  toleranceDistance: getDistance(100, 'meters'),
  debug: true, // required
  env: Environment.DEVELOPMENT | Environment.TEST,
  googleMapsApiKey?: string
});

console.log('HTML file:', result.htmlPath);
console.log('Route stats:', result.routeStats);
```

## 📊 API Reference

### ShortestDistanceAlgorithm

Shortest distance algorithm. Computes shortest distance from each Coordinate point to the route polyline.

```typescript
const result = getPointsOnRoute(
  polyline: PolylineString,
  points: Array<LatLng | H3Index>,
  tolerance: Distance,
  name?: AlgorithmName
);
```

### RadiusAlgorithm

Dynamic radius algorithm. Finds Coordinate points that fall inside radii around route points.

```typescript
const result = getPointsOnRoute(
  polyline: PolylineString,
  points: Array<LatLng | H3Index>,
  tolerance: Distance,
  name: AlgorithmName.RADIUS
);
```

### generateVisuals

Generates a tabbed HTML visualization comparing both algorithms.

```typescript
const result = await generateVisuals({
  polyline: PolylineString,
  points: Array<LatLng | H3Index>,
  toleranceDistance?: Distance,
  debug: true, 
  env: Environment.DEVELOPMENT | Environment.TEST,
  googleMapsApiKey?: string
});
```

Notes:
- googleMapsApiKey resolves as: param (if provided) → process.env.GOOGLE_MAPS_API_KEY.
- Consumers must set the env variable in their own environment.

## 🧪 Tests and Coverage

- Run tests: `npm test`
- Coverage: `npm run test:coverage` (V8 provider, text/json/html reporters)
- Goal: 100% meaningful coverage for core types and algorithms

Current coverage (Vitest + v8):

- Overall: Statements 76.03%, Branches 80.47%, Functions 80.00%, Lines 76.03%
- Algorithms: Statements 92.01%
- Spatial Grid: Statements 95.87%
- Functions: Statements 71.30%
- Geometry: Statements 59.01%
- Types: Statements 61.99%
- Visualization: Statements 67.07%

Guarantees:
- Polyline validation (incomplete varints, invalid chars, bounds)
- `Distance` and `Radius` value objects with strict validation against negative/NaN/undefined
- Spatial grid with O(1)/O(log n) candidate search; no nested loops
- `strictNullChecks` enabled; `undefined` rejected

## 🏗️ Project Structure

```
src/
├── domain/
│   ├── algorithms/
│   │   ├── base.algorithm.ts
│   │   ├── factory/
│   │   │   └── algorithm.factory.ts
│   │   ├── radius.algorithm.ts
│   │   └── shortest-distance.algorithm.ts
│   ├── constants/
│   │   └── algorithm.constants.ts
│   ├── functions/
│   │   ├── generate-visuals.ts
│   │   ├── get-points-on-route.ts
│   │   └── index.ts
│   ├── geometry/
│   │   ├── h3-to-route-point-calculator.ts
│   │   ├── h3-to-route-segment-calculator.ts
│   │   ├── haversine-calculator.ts
│   │   └── point-to-line-calculator.ts
│   ├── spatial/
│   │   └── spatial-grid.ts
│   ├── types/
│   │   ├── algorithm.types.ts
│   │   ├── coordinate.types.ts
│   │   ├── count.types.ts
│   │   ├── distance.types.ts
│   │   ├── polyline-string.types.ts
│   │   ├── radius.types.ts
│   │   ├── spatial.types.ts
│   │   └── visualization.types.ts
│   └── visualization/
│       └── visualization.ts
├── types.ts
└── domain/index.ts
```

## 📝 License

MIT License

## 🤝 Contributing

1. Fork the repo
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit (`git commit -m 'Add some amazing feature'`)
4. Push (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## 📞 Contact

GitHub Issues: https://github.com/berkcansavur/route-point-check/issues
