@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 +21 -0
- package/README.md +59 -0
- package/dist/cluster.d.ts +41 -0
- package/dist/cluster.js +110 -0
- package/dist/countries.d.ts +27 -0
- package/dist/countries.js +50 -0
- package/dist/engine.d.ts +84 -0
- package/dist/engine.js +3 -0
- package/dist/engines/google.engine.d.ts +51 -0
- package/dist/engines/google.engine.js +396 -0
- package/dist/engines/leaflet.engine.d.ts +13 -0
- package/dist/engines/leaflet.engine.js +232 -0
- package/dist/format.d.ts +13 -0
- package/dist/format.js +32 -0
- package/dist/geo.d.ts +64 -0
- package/dist/geo.js +136 -0
- package/dist/hours.d.ts +41 -0
- package/dist/hours.js +174 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +16 -0
- package/dist/marker-html.d.ts +37 -0
- package/dist/marker-html.js +119 -0
- package/dist/types.d.ts +238 -0
- package/dist/types.js +8 -0
- package/dist/url.d.ts +8 -0
- package/dist/url.js +20 -0
- package/package.json +55 -0
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;
|
package/dist/cluster.js
ADDED
|
@@ -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
|
+
}
|
package/dist/engine.d.ts
ADDED
|
@@ -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 = "© <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 = '© <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;
|