@konce-pt/map 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 konce.pt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,59 @@
1
+ <p align="center">
2
+ <img src="https://ui.konce.pt/images/koncept_ui_lib.png" alt="Koncept UI — Angular component library" width="100%" />
3
+ </p>
4
+
5
+ # @konce-pt/map
6
+
7
+ Rdzeń mapy [Koncept UI](https://gitlab.com/konce-pt/koncept-ui): odwzorowanie i arytmetyka
8
+ prostokątów, klastrowanie znaczników, godziny otwarcia, HTML znaczników, formatowanie — oraz
9
+ adaptery silników Leaflet i Google. Czysty TypeScript, **zero zależności runtime**
10
+ (`leaflet` to peer opcjonalny, ładowany dynamicznym `import()`).
11
+
12
+ Zwykle nie instalujesz go samodzielnie: używają go `@konce-pt/angular/map` i `@konce-pt/react/map`,
13
+ i oba re-eksportują jego API. Sięgnij po niego wprost, gdy liczysz klastry po stronie serwera albo
14
+ podpinasz własny silnik mapowy.
15
+
16
+ ```bash
17
+ npm i @konce-pt/map
18
+ ```
19
+
20
+ ```ts
21
+ import { clusterLocations, zoomForBounds, isOpenAt, createLeafletEngine } from '@konce-pt/map';
22
+
23
+ // Klastrowanie na siatce pikseli — wynik zależy od zoomu, nie od samych współrzędnych.
24
+ clusterLocations(locations, 12, { gridSize: 60 });
25
+
26
+ // Zoom, przy którym prostokąt mieści się w kontenerze.
27
+ zoomForBounds(bounds, { width: 800, height: 480 });
28
+
29
+ // Godziny otwarcia razem z zakresami przez północ.
30
+ isOpenAt(hours, new Date());
31
+
32
+ // Silnik: jedyne miejsce, które zna typy Leafleta.
33
+ const engine = createLeafletEngine({ maxZoom: 19 });
34
+ ```
35
+
36
+ ## API
37
+
38
+ - **Geometria** — `project`, `unproject`, `boundsOf`, `boundsCenter`, `boundsContain`, `padBounds`,
39
+ `isFittableBounds`, `haversine`, `metersPerPixel`, `zoomForBounds`, `clampLat`, `normalizeLng`
40
+ - **Klastry** — `clusterLocations`, `dominantCategory`, `countByCategory`, `isSplittable`
41
+ - **Znaczniki** — `renderMarkerHtml`, `renderClusterHtml`, `renderUserMarkerHtml`, `colorVarOf`,
42
+ `contrastVarOf`, `foregroundVarOf`, `clusterTier`
43
+ - **Godziny** — `parseTime`, `formatTime`, `isOpenAt`, `isAlwaysOpen`, `nextChange`, `groupWeek`
44
+ - **Kraje i format** — `KPT_MAP_COUNTRIES`, `countryPreset`, `resolveCountryView`,
45
+ `formatDistance`, `formatLatLng`, `normalizeUrl`, `prettyUrl`
46
+ - **Silniki** — `createLeafletEngine`, `createGoogleEngine`, `loadGoogleMapsApi`
47
+ - **Typy** — `KptMapEngine`, `KptMapLocation`, `KptMapCategory`, `KptMapPoi` i reszta kontraktu
48
+
49
+ Pełny opis: [`llms.txt`](./llms.txt) (EN) i [`llms-pl.txt`](./llms-pl.txt) (PL).
50
+
51
+ ## Testy
52
+
53
+ ```bash
54
+ pnpm --filter @konce-pt/map exec node --test "src/**/*.test.ts"
55
+ ```
56
+
57
+ ## Licencja
58
+
59
+ MIT © [konce.pt](https://konce.pt)
@@ -0,0 +1,41 @@
1
+ import type { KptMapCluster, KptMapClusterEntry, KptMapLocation } from './types.ts';
2
+ export interface KptClusterOptions {
3
+ /** Aktualny zoom mapy — decyduje o gęstości siatki. */
4
+ zoom: number;
5
+ /** Bok komórki siatki w pikselach ekranu. Domyślnie 60. */
6
+ gridSize?: number;
7
+ /** Powyżej tego zoomu grupowanie jest wyłączone. Domyślnie 16. */
8
+ maxZoom?: number;
9
+ /** Ile punktów musi trafić do komórki, żeby powstała grupa. Domyślnie 2. */
10
+ minPoints?: number;
11
+ /**
12
+ * Kolejność kategorii — rozstrzyga remis przy wyborze kategorii dominującej.
13
+ * Bez niej remis rozstrzyga kolejność wystąpienia w danych.
14
+ */
15
+ categoryOrder?: readonly string[];
16
+ }
17
+ export declare const KPT_CLUSTER_DEFAULTS: {
18
+ readonly gridSize: 60;
19
+ readonly maxZoom: 16;
20
+ readonly minPoints: 2;
21
+ };
22
+ /**
23
+ * Zwija bliskie sobie lokalizacje w grupy. Zwraca listę mieszaną: grupy i pojedyncze punkty.
24
+ *
25
+ * Środek grupy uśredniamy w przestrzeni pikseli, nie w stopniach. Mercator rozciąga
26
+ * stopnie tym mocniej, im dalej od równika, więc średnia w stopniach wypada obok
27
+ * wizualnego środka kropek na ekranie — a to jego ma pilnować znacznik grupy.
28
+ */
29
+ export declare function clusterLocations<T>(locations: readonly KptMapLocation<T>[], options: KptClusterOptions): KptMapClusterEntry<T>[];
30
+ /**
31
+ * Najliczniejsza kategoria w grupie. Remis rozstrzyga `categoryOrder`, a bez niej
32
+ * kolejność pierwszego wystąpienia — w obu wypadkach wynik jest deterministyczny.
33
+ */
34
+ export declare function dominantCategory<T>(items: readonly KptMapLocation<T>[], categoryOrder?: readonly string[]): string;
35
+ /** Zlicza lokalizacje w rozbiciu na kategorie — licznik obok pozycji legendy. */
36
+ export declare function countByCategory<T>(locations: readonly KptMapLocation<T>[]): Record<string, number>;
37
+ /**
38
+ * Czy grupa da się rozbić przybliżeniem. Punkty o tej samej współrzędnej zostaną razem
39
+ * na każdym zoomie — wtedy kliknięcie ma otworzyć listę zamiast zoomować w nieskończoność.
40
+ */
41
+ export declare function isSplittable<T>(cluster: KptMapCluster<T>): boolean;
@@ -0,0 +1,110 @@
1
+ /*
2
+ * Grupowanie znaczników — siatka w przestrzeni pikseli, O(n), deterministyczna.
3
+ * Pure TS: ten sam kod obsługuje silnik Leaflet i Google Maps, i przenosi się do Reacta.
4
+ */
5
+ import { boundsOf, project, unproject } from "./geo.js";
6
+ export const KPT_CLUSTER_DEFAULTS = {
7
+ gridSize: 60,
8
+ maxZoom: 16,
9
+ minPoints: 2,
10
+ };
11
+ /**
12
+ * Zwija bliskie sobie lokalizacje w grupy. Zwraca listę mieszaną: grupy i pojedyncze punkty.
13
+ *
14
+ * Środek grupy uśredniamy w przestrzeni pikseli, nie w stopniach. Mercator rozciąga
15
+ * stopnie tym mocniej, im dalej od równika, więc średnia w stopniach wypada obok
16
+ * wizualnego środka kropek na ekranie — a to jego ma pilnować znacznik grupy.
17
+ */
18
+ export function clusterLocations(locations, options) {
19
+ const gridSize = options.gridSize ?? KPT_CLUSTER_DEFAULTS.gridSize;
20
+ const maxZoom = options.maxZoom ?? KPT_CLUSTER_DEFAULTS.maxZoom;
21
+ const minPoints = Math.max(2, options.minPoints ?? KPT_CLUSTER_DEFAULTS.minPoints);
22
+ const zoom = options.zoom;
23
+ if (locations.length === 0)
24
+ return [];
25
+ // Powyżej progu pokazujemy wszystko osobno — użytkownik przybliżył się celowo.
26
+ if (zoom > maxZoom || gridSize <= 0)
27
+ return [...locations];
28
+ const cells = new Map();
29
+ for (const loc of locations) {
30
+ const p = project(loc.at, zoom);
31
+ const cellX = Math.floor(p.x / gridSize);
32
+ const cellY = Math.floor(p.y / gridSize);
33
+ const key = `${cellX}:${cellY}`;
34
+ const cell = cells.get(key);
35
+ if (cell) {
36
+ cell.items.push(loc);
37
+ cell.sumX += p.x;
38
+ cell.sumY += p.y;
39
+ }
40
+ else {
41
+ cells.set(key, { cellX, cellY, items: [loc], sumX: p.x, sumY: p.y });
42
+ }
43
+ }
44
+ const out = [];
45
+ for (const cell of cells.values()) {
46
+ if (cell.items.length < minPoints) {
47
+ out.push(...cell.items);
48
+ continue;
49
+ }
50
+ const center = unproject({ x: cell.sumX / cell.items.length, y: cell.sumY / cell.items.length }, zoom);
51
+ out.push({
52
+ id: `c:${zoom}:${cell.cellX}:${cell.cellY}`,
53
+ at: center,
54
+ count: cell.items.length,
55
+ dominantCategoryId: dominantCategory(cell.items, options.categoryOrder),
56
+ items: cell.items,
57
+ // Punkty w komórce zawsze istnieją, więc `boundsOf` nie zwróci tu null.
58
+ bounds: boundsOf(cell.items.map((i) => i.at)),
59
+ });
60
+ }
61
+ return out;
62
+ }
63
+ /**
64
+ * Najliczniejsza kategoria w grupie. Remis rozstrzyga `categoryOrder`, a bez niej
65
+ * kolejność pierwszego wystąpienia — w obu wypadkach wynik jest deterministyczny.
66
+ */
67
+ export function dominantCategory(items, categoryOrder) {
68
+ const counts = new Map();
69
+ const firstSeen = new Map();
70
+ items.forEach((item, index) => {
71
+ counts.set(item.categoryId, (counts.get(item.categoryId) ?? 0) + 1);
72
+ if (!firstSeen.has(item.categoryId))
73
+ firstSeen.set(item.categoryId, index);
74
+ });
75
+ let best = items[0].categoryId;
76
+ let bestCount = -1;
77
+ for (const [id, count] of counts) {
78
+ if (count > bestCount) {
79
+ best = id;
80
+ bestCount = count;
81
+ continue;
82
+ }
83
+ if (count < bestCount)
84
+ continue;
85
+ // Remis — niższy indeks w `categoryOrder` wygrywa, a poza nią wcześniejsze wystąpienie.
86
+ const rank = (c) => {
87
+ const i = categoryOrder?.indexOf(c) ?? -1;
88
+ return i >= 0 ? i : Number.MAX_SAFE_INTEGER;
89
+ };
90
+ const byOrder = rank(id) - rank(best);
91
+ if (byOrder < 0 || (byOrder === 0 && firstSeen.get(id) < firstSeen.get(best)))
92
+ best = id;
93
+ }
94
+ return best;
95
+ }
96
+ /** Zlicza lokalizacje w rozbiciu na kategorie — licznik obok pozycji legendy. */
97
+ export function countByCategory(locations) {
98
+ const out = {};
99
+ for (const loc of locations)
100
+ out[loc.categoryId] = (out[loc.categoryId] ?? 0) + 1;
101
+ return out;
102
+ }
103
+ /**
104
+ * Czy grupa da się rozbić przybliżeniem. Punkty o tej samej współrzędnej zostaną razem
105
+ * na każdym zoomie — wtedy kliknięcie ma otworzyć listę zamiast zoomować w nieskończoność.
106
+ */
107
+ export function isSplittable(cluster) {
108
+ const { south, west, north, east } = cluster.bounds;
109
+ return north - south > 1e-7 || east - west > 1e-7;
110
+ }
@@ -0,0 +1,27 @@
1
+ import type { KptLatLng, KptMapBounds } from './types.ts';
2
+ export interface KptMapCountryPreset {
3
+ /** Kod ISO 3166-1 alpha-2 (albo `EU` dla całego kontynentu). */
4
+ code: string;
5
+ center: KptLatLng;
6
+ /** Zoom, przy którym kraj mieści się na typowym kontenerze (~1000×600 px). */
7
+ zoom: number;
8
+ bounds: KptMapBounds;
9
+ }
10
+ /** Kod kraju użyty, gdy aplikacja nie poda żadnego. */
11
+ export declare const KPT_MAP_DEFAULT_COUNTRY = "PL";
12
+ /**
13
+ * Zasięgi lądowe (bez terytoriów zamorskich) zaokrąglone do dwóch miejsc —
14
+ * to widok startowy, nie granica administracyjna.
15
+ */
16
+ export declare const KPT_MAP_COUNTRIES: Record<string, KptMapCountryPreset>;
17
+ /** Preset kraju po kodzie (wielkość liter bez znaczenia); `undefined`, gdy nieznany. */
18
+ export declare function countryPreset(code: string): KptMapCountryPreset | undefined;
19
+ /** Kody wszystkich presetów — do listy wyboru w aplikacji. */
20
+ export declare function countryCodes(): string[];
21
+ /**
22
+ * Widok startowy: preset kraju, a przy nieznanym kodzie preset domyślny.
23
+ * Zawsze zwraca coś sensownego — mapa nigdy nie startuje na `[0, 0]`.
24
+ */
25
+ export declare function resolveCountryView(code: string | null | undefined): KptMapCountryPreset;
26
+ /** Środek prostokąta kraju — pomocnicze przy własnych `bounds`. */
27
+ export declare function centerOfBounds(bounds: KptMapBounds): KptLatLng;
@@ -0,0 +1,50 @@
1
+ /*
2
+ * Presety widoku bazowego — pure TS. Domyślny kraj to Polska.
3
+ * Kto potrzebuje kraju spoza listy, podaje `[view]` albo `[bounds]` wprost.
4
+ */
5
+ import { boundsCenter } from "./geo.js";
6
+ /** Kod kraju użyty, gdy aplikacja nie poda żadnego. */
7
+ export const KPT_MAP_DEFAULT_COUNTRY = 'PL';
8
+ /**
9
+ * Zasięgi lądowe (bez terytoriów zamorskich) zaokrąglone do dwóch miejsc —
10
+ * to widok startowy, nie granica administracyjna.
11
+ */
12
+ export const KPT_MAP_COUNTRIES = {
13
+ PL: { code: 'PL', center: { lat: 52.07, lng: 19.48 }, zoom: 6, bounds: { south: 49.0, west: 14.07, north: 54.84, east: 24.15 } },
14
+ DE: { code: 'DE', center: { lat: 51.17, lng: 10.45 }, zoom: 6, bounds: { south: 47.27, west: 5.87, north: 55.06, east: 15.04 } },
15
+ CZ: { code: 'CZ', center: { lat: 49.82, lng: 15.47 }, zoom: 7, bounds: { south: 48.55, west: 12.09, north: 51.06, east: 18.86 } },
16
+ SK: { code: 'SK', center: { lat: 48.67, lng: 19.7 }, zoom: 7, bounds: { south: 47.73, west: 16.83, north: 49.61, east: 22.57 } },
17
+ UA: { code: 'UA', center: { lat: 48.38, lng: 31.17 }, zoom: 6, bounds: { south: 44.39, west: 22.14, north: 52.38, east: 40.23 } },
18
+ LT: { code: 'LT', center: { lat: 55.17, lng: 23.88 }, zoom: 7, bounds: { south: 53.9, west: 20.94, north: 56.45, east: 26.84 } },
19
+ LV: { code: 'LV', center: { lat: 56.88, lng: 24.6 }, zoom: 7, bounds: { south: 55.67, west: 20.97, north: 58.09, east: 28.24 } },
20
+ GB: { code: 'GB', center: { lat: 54.0, lng: -2.9 }, zoom: 5, bounds: { south: 49.9, west: -8.65, north: 60.86, east: 1.77 } },
21
+ FR: { code: 'FR', center: { lat: 46.6, lng: 2.45 }, zoom: 6, bounds: { south: 41.33, west: -5.14, north: 51.09, east: 9.56 } },
22
+ ES: { code: 'ES', center: { lat: 40.2, lng: -3.3 }, zoom: 6, bounds: { south: 35.95, west: -9.3, north: 43.79, east: 3.32 } },
23
+ IT: { code: 'IT', center: { lat: 42.5, lng: 12.5 }, zoom: 6, bounds: { south: 36.65, west: 6.63, north: 47.09, east: 18.52 } },
24
+ NL: { code: 'NL', center: { lat: 52.2, lng: 5.5 }, zoom: 7, bounds: { south: 50.75, west: 3.36, north: 53.56, east: 7.23 } },
25
+ BE: { code: 'BE', center: { lat: 50.64, lng: 4.66 }, zoom: 8, bounds: { south: 49.5, west: 2.54, north: 51.5, east: 6.41 } },
26
+ AT: { code: 'AT', center: { lat: 47.7, lng: 13.35 }, zoom: 7, bounds: { south: 46.37, west: 9.53, north: 49.02, east: 17.16 } },
27
+ SE: { code: 'SE', center: { lat: 62.2, lng: 16.3 }, zoom: 4, bounds: { south: 55.34, west: 11.11, north: 69.06, east: 24.16 } },
28
+ NO: { code: 'NO', center: { lat: 64.5, lng: 12.0 }, zoom: 4, bounds: { south: 57.98, west: 4.65, north: 71.19, east: 31.08 } },
29
+ US: { code: 'US', center: { lat: 39.5, lng: -98.35 }, zoom: 4, bounds: { south: 24.4, west: -125.0, north: 49.38, east: -66.93 } },
30
+ EU: { code: 'EU', center: { lat: 54.0, lng: 15.0 }, zoom: 4, bounds: { south: 34.8, west: -11.0, north: 71.2, east: 40.2 } },
31
+ };
32
+ /** Preset kraju po kodzie (wielkość liter bez znaczenia); `undefined`, gdy nieznany. */
33
+ export function countryPreset(code) {
34
+ return KPT_MAP_COUNTRIES[code?.toUpperCase()];
35
+ }
36
+ /** Kody wszystkich presetów — do listy wyboru w aplikacji. */
37
+ export function countryCodes() {
38
+ return Object.keys(KPT_MAP_COUNTRIES);
39
+ }
40
+ /**
41
+ * Widok startowy: preset kraju, a przy nieznanym kodzie preset domyślny.
42
+ * Zawsze zwraca coś sensownego — mapa nigdy nie startuje na `[0, 0]`.
43
+ */
44
+ export function resolveCountryView(code) {
45
+ return (code ? countryPreset(code) : undefined) ?? KPT_MAP_COUNTRIES[KPT_MAP_DEFAULT_COUNTRY];
46
+ }
47
+ /** Środek prostokąta kraju — pomocnicze przy własnych `bounds`. */
48
+ export function centerOfBounds(bounds) {
49
+ return boundsCenter(bounds);
50
+ }
@@ -0,0 +1,84 @@
1
+ import type { KptLatLng, KptMapBounds, KptMapPoint, KptMapUserPosition } from './types.ts';
2
+ /** Rodzaj silnika — trafia do `data-engine` na hoście, przydatne w testach i stylach. */
3
+ export type KptMapEngineKind = 'leaflet' | 'google';
4
+ export interface KptMapEngineInit {
5
+ center: KptLatLng;
6
+ zoom: number;
7
+ minZoom?: number;
8
+ maxZoom?: number;
9
+ /** Blokada panningu poza prostokąt. */
10
+ maxBounds?: KptMapBounds | null;
11
+ /** Szablon URL kafli, np. `https://tile.openstreetmap.org/{z}/{x}/{y}.png`. */
12
+ tileUrl: string;
13
+ /** Tekst atrybucji — wymagany licencyjnie przy OSM. */
14
+ attribution: string;
15
+ /** Silnik nie rysuje własnych kontrolek zoomu — robi to warstwa Angulara. */
16
+ interactive?: boolean;
17
+ }
18
+ /** Znacznik gotowy do narysowania — HTML pochodzi z `marker-html.ts`. */
19
+ export interface KptMapRenderedMarker {
20
+ /** Stabilne id: `id` lokalizacji albo `id` grupy. */
21
+ id: string;
22
+ at: KptLatLng;
23
+ html: string;
24
+ /** Rozmiar ikony w pikselach — silnik potrzebuje go do zakotwiczenia. */
25
+ size: {
26
+ width: number;
27
+ height: number;
28
+ };
29
+ /** Punkt zakotwiczenia względem lewego górnego rogu ikony. */
30
+ anchor: KptMapPoint;
31
+ /** Kolejność rysowania — grupy nad pojedynczymi punktami. */
32
+ zIndex?: number;
33
+ }
34
+ export interface KptMapPointerEvent {
35
+ at: KptLatLng;
36
+ /** Piksele względem kontenera mapy. */
37
+ point: KptMapPoint;
38
+ /** Zdarzenie źródłowe, gdy trzeba zatrzymać propagację. */
39
+ originalEvent?: Event;
40
+ }
41
+ export interface KptMapEngineEvents {
42
+ /** Pan i zoom w toku — używane do przeliczania pozycji popupu. */
43
+ move: () => void;
44
+ /** Koniec ruchu — dopiero tu warto emitować `viewChange`. */
45
+ moveend: () => void;
46
+ zoomend: () => void;
47
+ click: (event: KptMapPointerEvent) => void;
48
+ contextmenu: (event: KptMapPointerEvent) => void;
49
+ /** Kliknięcie w znacznik; `id` jak w `KptMapRenderedMarker`. */
50
+ markerclick: (id: string, event: KptMapPointerEvent) => void;
51
+ }
52
+ export interface KptMapEngine {
53
+ readonly kind: KptMapEngineKind;
54
+ /** Tworzy mapę w kontenerze. Wywoływane po pierwszym renderze, nigdy na SSR. */
55
+ init(host: HTMLElement, opts: KptMapEngineInit): Promise<void>;
56
+ destroy(): void;
57
+ setView(center: KptLatLng, zoom: number, animate?: boolean): void;
58
+ fitBounds(bounds: KptMapBounds, paddingPx?: number): void;
59
+ panBy(offset: KptMapPoint): void;
60
+ getCenter(): KptLatLng;
61
+ getZoom(): number;
62
+ getBounds(): KptMapBounds;
63
+ getSize(): {
64
+ width: number;
65
+ height: number;
66
+ };
67
+ /** Współrzędne → piksele WZGLĘDEM kontenera (pozycjonowanie popupu). */
68
+ project(at: KptLatLng): KptMapPoint;
69
+ unproject(point: KptMapPoint): KptLatLng;
70
+ /** Pełna wymiana zestawu znaczników; diff po `id` robi implementacja. */
71
+ setMarkers(markers: readonly KptMapRenderedMarker[]): void;
72
+ /** `null` chowa znacznik użytkownika i krąg dokładności. */
73
+ setUserPosition(position: KptMapUserPosition | null, html: string): void;
74
+ setTileSource(tileUrl: string, attribution: string): void;
75
+ setMaxBounds(bounds: KptMapBounds | null): void;
76
+ /** Kontener zmienił rozmiar — bez tego Leaflet zostawia szare pola. */
77
+ invalidateSize(): void;
78
+ on<K extends keyof KptMapEngineEvents>(event: K, handler: KptMapEngineEvents[K]): void;
79
+ }
80
+ /** Fabryka silnika — to ją dostarcza DI, żeby każda mapa miała własną instancję. */
81
+ export type KptMapEngineFactory = () => KptMapEngine;
82
+ /** Domyślne źródło kafli. Do produkcji podstaw własny serwer — patrz llms.txt. */
83
+ export declare const KPT_MAP_DEFAULT_TILE_URL = "https://tile.openstreetmap.org/{z}/{x}/{y}.png";
84
+ export declare const KPT_MAP_DEFAULT_ATTRIBUTION = "&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors";
package/dist/engine.js ADDED
@@ -0,0 +1,3 @@
1
+ /** Domyślne źródło kafli. Do produkcji podstaw własny serwer — patrz llms.txt. */
2
+ export const KPT_MAP_DEFAULT_TILE_URL = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png';
3
+ export const KPT_MAP_DEFAULT_ATTRIBUTION = '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors';
@@ -0,0 +1,51 @@
1
+ import type { KptMapEngine } from '../engine.ts';
2
+ /**
3
+ * Namespace `google.maps`. Świadomie `any` — pełne typy przychodzą z opcjonalnego
4
+ * `@types/google.maps`, a biblioteka nie może od nich zależeć przy kompilacji.
5
+ */
6
+ export type KptGoogleMapsApi = any;
7
+ export interface KptGoogleOptions {
8
+ /** Klucz API z Google Cloud. Wymagany. */
9
+ apiKey: string;
10
+ /**
11
+ * Identyfikator stylu mapy z konsoli Google (*Map management → Create Map ID*).
12
+ *
13
+ * **Wymagany.** Bez niego nie działa `AdvancedMarkerElement`, a znaczniki tej biblioteki
14
+ * to gotowy HTML z `marker-html.ts` — starsze `Marker` nie umie go przyjąć.
15
+ */
16
+ mapId: string;
17
+ /** Wersja API. Domyślnie `weekly`. */
18
+ version?: string;
19
+ /** Język etykiet na mapie (kod ISO 639-1). Domyślnie język przeglądarki. */
20
+ language?: string;
21
+ /** Region wpływający na nazewnictwo i granice sporne (ISO 3166-1 alpha-2). */
22
+ region?: string;
23
+ /** Dolny limit zoomu. Domyślnie 2 — niżej świat powtarza się w poziomie. */
24
+ minZoom?: number;
25
+ /** Górny limit zoomu. Domyślnie 21. */
26
+ maxZoom?: number;
27
+ /** Zoom kółkiem myszy. Domyślnie włączony. */
28
+ scrollWheelZoom?: boolean;
29
+ }
30
+ /**
31
+ * Tyle z `KptGoogleOptions`, ile trzeba do wczytania samego API. Bez `mapId` — ten dotyczy
32
+ * wyglądu mapy, a API potrzebują też usługi, które żadnej mapy nie rysują.
33
+ */
34
+ export type KptGoogleLoadOptions = Pick<KptGoogleOptions, 'apiKey' | 'version' | 'language' | 'region'>;
35
+ /**
36
+ * Ładuje Google Maps JavaScript API raz na aplikację. Gdy `google.maps` już istnieje — bo
37
+ * aplikacja wstawiła skrypt sama albo stoi tu druga mapa — korzystamy z tego, co jest: drugie
38
+ * wczytanie Google i tak odrzuca, wypisując ostrzeżenie do konsoli.
39
+ *
40
+ * Publiczne, bo silnik nie jest jedynym, kto tego API potrzebuje. Geokoder Google **musi**
41
+ * iść przez SDK, a nie przez REST: web service `/maps/api/geocode/json` odrzuca klucze
42
+ * z restrykcją `Referer` („API keys with referer restrictions cannot be used with this API"),
43
+ * a to jedyna restrykcja, jaką da się sensownie nałożyć na klucz leżący w bundlu przeglądarki.
44
+ * Wywołania z załadowanego SDK autoryzują się tak samo jak sama mapa.
45
+ *
46
+ * Adaptera geokodera biblioteka nadal nie dostarcza — tylko drogę do API, którą i tak
47
+ * otwiera silnikowi. Powody stoją przy `provideKptGoogleMap` i w `llms.txt`.
48
+ */
49
+ export declare function loadGoogleMapsApi(options: KptGoogleLoadOptions): Promise<KptGoogleMapsApi>;
50
+ /** Fabryka silnika — każda mapa dostaje własną instancję. */
51
+ export declare function createGoogleEngine(options: KptGoogleOptions): KptMapEngine;