# @geolonia/maps-suite

High-level map components for [Geolonia Maps](https://geolonia.com/). Built on `@geolonia/maps-core`, providing familiar classes like `Map`, `Marker`, `InfoWindow`, and `OverlayView` with a simple, imperative API.

## Install

```bash
npm install @geolonia/maps-suite maplibre-gl
```

`maplibre-gl` is a peer dependency and must be installed alongside this package. `@geolonia/maps-core` is included as a dependency.

## Usage

```typescript
import { geolonia } from "@geolonia/maps-suite";

const { Map } = await geolonia.maps.importLibrary("maps");

const map = new geolonia.maps.Map(document.getElementById("map"), {
  center: { lat: 35.6812, lng: 139.7671 },
  zoom: 14,
  apiKey: "YOUR-API-KEY",
});
```

## API

### `importLibrary(name)`

Asynchronously loads a library of classes by name.

| Library | Exports |
|---------|---------|
| `"maps"` | `Map`, `MapElement`, `OverlayView`, `Polyline`, `Polygon`, `Rectangle`, `Circle`, `InfoWindow`, `Data` |
| `"marker"` | `Marker`, `AdvancedMarkerElement`, `MarkerClusterer` |
| `"core"` | `LatLng`, `LatLngBounds`, `Point`, `Size`, `MVCObject`, `event` |

### `MapElement`

A custom HTML element `<geolonia-map>` that initializes a `Map` instance internally. Supports declarative attributes for initial configuration.

```html
<style>
  geolonia-map {
    display: block;
    width: 100%;
    height: 400px;
  }
</style>
...
<geolonia-map
  center="35.6812,139.7671"
  zoom="14"
  id="my-map"
  map-id="my-map"
  map-style="geolonia/basic-v2"
  api-key="YOUR-API-KEY"
/>
```

After the element is connected, you can access the internal `Map` instance via the `innerMap` property.

```typescript
const mapElement = document.getElementById("my-map") as geolonia.maps.MapElement;
const map = mapElement.innerMap;
```

`geolonia-maps` supports disabling tilt and heading interactions via the `tilt-interaction-disabled` and `heading-interaction-disabled` boolean attributes.

### `Map`

The main map class. Internally uses `GeoloniaMap` from maps-core.

```typescript
const map = new geolonia.maps.Map(document.getElementById("map"), {
  center: { lat: 35.6812, lng: 139.7671 },
  zoom: 14,
  style: "geolonia/basic-v2",
  apiKey: "YOUR-API-KEY",
});

map.setCenter({ lat: 34.6937, lng: 135.5023 });
map.setZoom(12);
map.fitBounds({ north: 35.7, south: 35.6, east: 139.8, west: 139.7 });
```

### `Data`

A layer that holds GeoJSON features and renders them on the map. Unlike the individual
shape classes (`Marker` / `Polyline` / `Polygon`), it accepts a `FeatureCollection`
with mixed geometry types as-is.

Usually accessed through `map.data`, which is created lazily on first access and
attached to that map.

```typescript
const features = map.data.addGeoJson({
  type: "FeatureCollection",
  features: [
    {
      type: "Feature",
      id: "tokyo-station",
      geometry: { type: "Point", coordinates: [139.7671, 35.6812] },
      properties: { name: "東京駅", category: "station" },
    },
  ],
});

// Static style for every feature
map.data.setStyle({ fillColor: "#ff0000", strokeWeight: 2 });

// Or per-feature style
map.data.setStyle((feature) => ({
  fillColor: feature.getProperty("category") === "station" ? "#ff0000" : "#0000ff",
  visible: feature.getProperty("hidden") !== true,
}));

map.data.getFeatureById("tokyo-station");
map.data.forEach((feature) => console.log(feature.getProperty("name")));
map.data.remove(features[0]);
map.data.toGeoJson((geojson) => console.log(geojson));
```

| Method | Description |
|--------|-------------|
| `add(feature)` | Adds a single `DataFeature` (or `DataFeatureOptions`) and returns it |
| `addGeoJson(geojson)` | Adds a `FeatureCollection` or `Feature`, returns the added `DataFeature[]` |
| `remove(feature)` | Removes a feature. Unknown features are ignored |
| `forEach(callback)` | Iterates over all features |
| `getFeatureById(id)` | Looks a feature up by id |
| `toGeoJson(callback)` | Passes the current contents to the callback as a `FeatureCollection` |
| `setStyle(styleOrFn)` | Sets a static style, or a function returning a style per feature |
| `getStyle()` | Returns the current style |
| `setMap(map)` / `getMap()` | Attaches to or detaches from a map |

`DataStyleOptions` accepts `fillColor`, `fillOpacity`, `strokeColor`, `strokeOpacity`,
`strokeWeight` and `visible`. Styles are resolved per feature and baked into the source
data, so a style function can branch on each feature's properties.

`DataFeature` provides `getId`, `getGeometry`, `setGeometry`, `getProperty`,
`setProperty`, `removeProperty`, `forEachProperty` and `toGeoJson`.

### `Marker`

```typescript
const marker = new geolonia.maps.Marker({
  position: { lat: 35.6812, lng: 139.7671 },
  map: map,
  title: "Tokyo Station",
  icon: { url: "https://example.com/icon.png" },
});
```

### `AdvancedMarkerElement`

A marker with custom HTML content.

```typescript
const content = document.createElement("div");
content.textContent = "📍";

const marker = new geolonia.maps.AdvancedMarkerElement({
  position: { lat: 35.6812, lng: 139.7671 },
  map: map,
  content: content,
});
```

### `InfoWindow`

```typescript
const infoWindow = new geolonia.maps.InfoWindow({
  content: "<h3>Tokyo Station</h3><p>Central Tokyo</p>",
});

marker.addListener("click", () => {
  infoWindow.open({ map, anchor: marker });
});
```

### `MarkerClusterer`

Groups nearby markers into clusters using MapLibre's built-in GeoJSON clustering.

```typescript
const clusterer = new geolonia.maps.MarkerClusterer({
  map: map,
  markers: [marker1, marker2, marker3],
});
```

### `OverlayView`

Base class for implementing custom overlays.

```typescript
class CustomOverlay extends geolonia.maps.OverlayView {
  onAdd() { /* Add DOM elements to panes */ }
  draw() { /* Update position */ }
  onRemove() { /* Remove DOM elements */ }
}
```

### `MVCObject`

Base class providing property binding and event notification. All components inherit from this class.

### Types

| Type | Description |
|------|-------------|
| `LatLng` | Represents a latitude/longitude pair |
| `LatLngBounds` | Represents a rectangular geographical bounds |
| `LatLngLiteral` | `{ lat: number, lng: number }` |
| `LatLngBoundsLiteral` | `{ north, south, east, west }` |

## Related Packages

| Package | Description |
|---------|-------------|
| `@geolonia/maps-core` | Core library extending MapLibre GL JS |

## Development

```bash
npm install
npm run dev       # Watch build + dev server (http://localhost:3030/e2e/)
npm run build     # Rollup (dev + production)
npm run test      # Vitest unit tests
npm run e2e       # Playwright E2E tests
npm run lint      # Biome
npm run test:coverage # Vitest with coverage report
```

## License

MIT
