npm package: @navios-sdk/web
Description: Browser maps SDK with MapLibre GL — the open source Google Maps alternative
Author: naviOS
Latest version: 1.2.0

# @navios-sdk/web

Browser maps SDK with MapLibre GL — the open source Google Maps alternative.

Built on [OpenStreetMap](https://openstreetmap.org), [Valhalla](https://github.com/valhalla/valhalla), [MapLibre GL](https://maplibre.org), and [Nominatim](https://nominatim.org). No API keys required.

[![npm version](https://img.shields.io/npm/v/@navios-sdk/web.svg)](https://www.npmjs.com/package/@navios-sdk/web)
[![license](https://img.shields.io/npm/l/@navios-sdk/web.svg)](https://opensource.org/licenses/MIT)

> **Headless / server-side only?** Use [`@navios-sdk/core`](https://www.npmjs.com/package/@navios-sdk/core) for routing, geocoding, and autocomplete without map rendering.

## Installation

```bash
npm install @navios-sdk/web maplibre-gl
```

## Quick Start

```typescript
import { Navios } from '@navios-sdk/web'

const nav = new Navios()

// Create a map
const map = await nav.map({
  container: 'map',
  center: { lat: 48.8566, lng: 2.3522 },
  zoom: 12
})

// Geocode an address
const result = await nav.geocode({ address: 'Eiffel Tower, Paris' })
console.log(result.lat, result.lng, result.displayName)

// Calculate a route and draw it
const route = await nav.route({
  origin: { lat: 48.8566, lng: 2.3522 },
  destination: { lat: 48.8606, lng: 2.3376 },
  mode: 'walk'
})

map.addMarker({ ...result, color: '#22ff88', label: 'Eiffel Tower' })
map.drawRoute(route.polyline)
map.fitRoute(route.polyline)
```

## Features

Everything in [`@navios-sdk/core`](https://www.npmjs.com/package/@navios-sdk/core), plus:

- **Interactive maps** — GPU-accelerated vector maps via MapLibre GL
- **Markers** — colored pins, labels, draggable, custom HTML elements
- **Route rendering** — draw routes with customizable colors, width, opacity, and outlines
- **Viewport control** — pan, zoom, flyTo, fitRoute, pitch, bearing
- **Offline maps** — precache tile regions for full offline map rendering
- **Service worker** — cache-first tiles, network-first styles, automatic registration
- **Request queue** — failed API calls queued and auto-replayed on reconnection
- **Event system** — connectivity changes, precache progress, queue replays
- **Dynamic imports** — MapLibre GL loaded on demand for optimal bundle splitting

### From core:

- **Geocoding** — forward and reverse via Nominatim
- **Autocomplete** — real-time search suggestions via Photon
- **Routing** — turn-by-turn directions (drive, walk, bicycle) via Valhalla
- **Retry + fallback** — exponential backoff with multi-URL fallback
- **LRU caching** — in-memory + optional persistent storage
- **Typed errors** — structured `NaviosError` with codes and service info
- **Zero API keys** — works immediately with public OpenStreetMap infrastructure

## Configuration

```typescript
const nav = new Navios({
  // Service URLs — string or string[] for fallback
  valhallaUrl: ['https://primary.example.com', 'https://backup.example.com'],
  nominatimUrl: 'https://your-nominatim.example.com',

  // Retry — exponential backoff on transient failures
  retry: { maxRetries: 3, baseDelay: 300, maxDelay: 5000 },

  // In-memory LRU cache
  cache: { enabled: true, maxSize: 100, ttl: 300000 },

  // Offline persistence (auto-injects IndexedDB in browser)
  offline: {
    enabled: true,
    storageTtl: 86400000,
    staleWhileOffline: true,
    queueRequests: true
  }
})
```

## Map API

### Creating a Map

```typescript
const map = await nav.map({
  container: 'map',         // DOM element ID or reference
  center: { lat: 40.7128, lng: -74.0060 },
  zoom: 13,
  pitch: 45,
  bearing: -17,
  style: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json'
})
```

### Markers

```typescript
const marker = map.addMarker({
  lat: 48.8584,
  lng: 2.2945,
  color: '#22ff88',
  label: 'Eiffel Tower',
  draggable: true
})

marker.setPosition({ lat: 48.86, lng: 2.35 })
marker.getPosition()  // { lat, lng }
marker.remove()
```

### Route Display

```typescript
map.drawRoute(route.polyline, {
  color: '#6366f1',
  width: 6,
  opacity: 0.85,
  outlineColor: '#000',
  outlineWidth: 2
})

map.fitRoute(route.polyline)
map.clearRoute()
```

### Viewport

```typescript
map.setCenter({ lat: 51.5074, lng: -0.1278 })
map.setZoom(15)
map.flyTo({ lat: 35.6762, lng: 139.6503 }, 14)
map.getCenter()  // { lat, lng }
map.getZoom()    // number
```

### Lifecycle

```typescript
map.resize()           // After container resize
map.remove()           // Clean up everything
map.getMapInstance()   // Access underlying MapLibre GL instance
```

## Navigation Example

Full workflow — geocode, route, draw:

```typescript
import { Navios } from '@navios-sdk/web'

const nav = new Navios()
const map = await nav.map({ container: 'map' })

const origin = await nav.geocode({ address: 'Eiffel Tower' })
const dest = await nav.geocode({ address: 'Louvre Museum' })

const route = await nav.route({ origin, destination: dest, mode: 'walk' })

map.addMarker({ ...origin, color: '#22ff88', label: 'Start' })
map.addMarker({ ...dest, color: '#ff4444', label: 'End' })
map.drawRoute(route.polyline)
map.fitRoute(route.polyline)

route.steps.forEach((step, i) => {
  console.log(`${i + 1}. ${step.instruction} (${step.distanceText})`)
})
```

## Offline Support

### Tile Precaching

```typescript
import { estimateTileCount } from '@navios-sdk/web'

const bounds = { north: 48.92, south: 48.80, east: 2.42, west: 2.25 }
const count = estimateTileCount(bounds, 10, 14)

const result = await nav.offline.precacheRegion({
  bounds,
  tileUrl: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
  minZoom: 10,
  maxZoom: 14,
  concurrency: 6,
  onProgress: (done, total) => console.log(`${done}/${total} tiles`)
})
```

### Service Worker

```typescript
await nav.offline.enable()

const stats = await nav.offline.tileStats()
console.log(stats.tileCount)

await nav.offline.clearTiles()
await nav.offline.clearAll()
await nav.offline.disable()
```

### Connectivity Events

```typescript
nav.offline.on('online', () => console.log('Back online!'))
nav.offline.on('offline', () => console.log('Gone offline'))
nav.offline.on('replayed', (results) => {
  console.log(`Replayed ${results.length} queued requests`)
})
```

## Error Handling

```typescript
import { NaviosError, NaviosErrorCode } from '@navios-sdk/core'

try {
  const result = await nav.geocode({ address: 'xyznonexistent' })
} catch (err) {
  if (err instanceof NaviosError) {
    console.log(err.code)      // 'NOT_FOUND'
    console.log(err.service)   // 'nominatim'
    console.log(err.retryable) // false
  }
}
```

## Map Styles

Any MapLibre GL style URL works:

```typescript
// Light (default) — CartoDB Voyager
await nav.map({ container: 'map' })

// Dark mode — CartoDB Dark Matter
await nav.map({
  container: 'map',
  style: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json'
})

// Positron — minimal
await nav.map({
  container: 'map',
  style: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json'
})
```

## Peer Dependencies

| Package | Version |
|---|---|
| `maplibre-gl` | `>=4.0.0` |

## Documentation

Full documentation with interactive playground: [openai4africa.org](https://openai4africa.org)

## License

[MIT](https://opensource.org/licenses/MIT)