Skip to content

Commit 8f2fd6c

Browse files
committed
feat init map
1 parent fe83fd3 commit 8f2fd6c

36 files changed

+425
-1017
lines changed

package-lock.json

Lines changed: 26 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@
7777
"vite-plugin-simple-manifest": "^1.0.3"
7878
},
7979
"dependencies": {
80+
"@helsingborg-stad/openstreetmap": "^0.53.4",
8081
"@splidejs/splide": "^4.1.4",
81-
"leaflet": "^1.9.3",
8282
"material-symbols": "^0.34.1",
8383
"vite-config-factory": "^1.1.2"
8484
},
@@ -111,4 +111,4 @@
111111
"publishConfig": {
112112
"registry": "https://npm.pkg.github.com/"
113113
}
114-
}
114+
}

source/components/map/index.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import MapFactory from './mapFactory';
2+
3+
export function initializeMaps() {
4+
document.querySelectorAll('[data-js-map]').forEach((mapContainer) => {
5+
const id = mapContainer.getAttribute('data-js-map');
6+
const provider = mapContainer.getAttribute('data-js-map-provider');
7+
const mapStyle = mapContainer.getAttribute('data-js-map-style');
8+
const lat = mapContainer.getAttribute('data-js-map-lat');
9+
const lng = mapContainer.getAttribute('data-js-map-lng');
10+
const zoom = mapContainer.getAttribute('data-js-map-zoom');
11+
const markers = mapContainer.getAttribute('data-js-map-markers');
12+
13+
if (!id || !provider || !lat || !lng) {
14+
console.warn('Map element is missing required attributes: data-js-map and data-js-map-provider');
15+
return;
16+
}
17+
18+
const args: MapArgs = {
19+
container: mapContainer as HTMLElement,
20+
id: id,
21+
lat: lat,
22+
lng: lng,
23+
style: mapStyle,
24+
zoom: zoom,
25+
markers: markers,
26+
};
27+
28+
// Only provider supported for now is OpenStreetMap.
29+
MapFactory.create(provider, args);
30+
});
31+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
class MapFactory {
2+
private static providers: Record<string, any> = {};
3+
4+
/**
5+
* Creates a map instance for the specified provider using the provided arguments.
6+
* If the provider's factory has not been loaded yet, it dynamically imports
7+
* the module and initializes the factory before creating the map instance.
8+
*
9+
* @param provider - The name of the map provider (e.g., "openstreetmap").
10+
* @param args - Configuration arguments required to create the map instance.
11+
* @returns A promise that resolves to the created map instance.
12+
*/
13+
public static async create(provider: string, args: MapArgs) {
14+
const key = provider.toLowerCase();
15+
16+
if (!MapFactory.providers[key]) {
17+
switch (key) {
18+
case 'openstreetmap':
19+
default: {
20+
const module = await import(`./providers/openstreetmap/openstreetmapFactory`);
21+
22+
MapFactory.providers[key] = new module.default();
23+
break;
24+
}
25+
}
26+
}
27+
28+
return MapFactory.providers[key].create(args);
29+
}
30+
}
31+
32+
export default MapFactory;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
type MapArgs = {
2+
container: HTMLElement;
3+
id: string;
4+
lat: string;
5+
lng: string;
6+
zoom: string | null;
7+
style: string | null;
8+
markers: string | null;
9+
};
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import type { CreateMarkerInterface, CreatePopupInterface, IconOptions, MarkerInterface } from '@helsingborg-stad/openstreetmap';
2+
import type { CreateMarker, MarkerConfig } from './openstreetmapInterface';
3+
4+
class Marker implements CreateMarker {
5+
/**
6+
* @param markerCreator - Factory used to create map marker instances.
7+
* @param popupCreator - Factory used to create popup instances bound to markers.
8+
*/
9+
constructor(
10+
private markerCreator: CreateMarkerInterface,
11+
private popupCreator: CreatePopupInterface,
12+
) {}
13+
14+
/**
15+
* Creates a map marker at the position defined in options. When a content
16+
* string is provided, a popup is bound to the marker and click listeners
17+
* are attached to toggle the highlighted icon state.
18+
*
19+
* @param options - Configuration for the marker's position, icon, color, and popup content.
20+
* @returns The created MarkerInterface instance.
21+
*/
22+
public create(options: MarkerConfig): MarkerInterface {
23+
const marker = this.markerCreator.create({
24+
position: { lat: options.lat, lng: options.lng },
25+
...this.getIconOptions(this.getMarkerContent(options)),
26+
});
27+
28+
if (options.content) {
29+
const popup = this.popupCreator.create();
30+
popup.bindTo(marker);
31+
popup.setContent(options.content);
32+
33+
this.addClickListener(marker, options);
34+
}
35+
36+
return marker;
37+
}
38+
39+
/**
40+
* Attaches popupopen and popupclose event listeners to the marker so that
41+
* its icon switches to the highlighted state while the popup is open and
42+
* reverts to the default state when the popup closes.
43+
*
44+
* @param marker - The marker to attach listeners to.
45+
* @param options - Marker configuration used to regenerate the icon HTML.
46+
*/
47+
private addClickListener(marker: MarkerInterface, options: MarkerConfig) {
48+
marker.addListener('popupopen', () => {
49+
marker.setIcon(this.getIconOptions(this.getHighlightedMarkerContent(options)));
50+
});
51+
52+
marker.addListener('popupclose', () => {
53+
marker.setIcon(this.getIconOptions(this.getMarkerContent(options)));
54+
});
55+
}
56+
57+
/**
58+
* Builds an IconOptions object with a fixed 32×32 size and a centred
59+
* horizontal anchor at [16, 2].
60+
*
61+
* @param html - The HTML string to use as the icon's visual content.
62+
* @returns An IconOptions object ready to pass to the marker creator.
63+
*/
64+
private getIconOptions(html: string): IconOptions {
65+
return {
66+
html: html,
67+
iconSize: [32, 32],
68+
iconAnchor: [16, 2],
69+
};
70+
}
71+
72+
/**
73+
* Generates the HTML string for the highlighted (active/open-popup) marker
74+
* icon, rendered as a white circle with a coloured border and icon.
75+
*
76+
* @param options - Marker configuration supplying colour and icon values.
77+
* @returns An HTML string representing the highlighted marker icon.
78+
*/
79+
private getHighlightedMarkerContent(options: MarkerConfig): string {
80+
const [color, icon] = this.getColorAndIcon(options);
81+
82+
return `<span style="background-color: white; border: solid 2px ${color}; color: ${color}; font-size: 20px; padding: 4px; border-radius: 50%;" data-material-symbol="${icon}" class="interactive-map__highlighted-marker material-symbols material-symbols-rounded material-symbols-sharp material-symbols-outlined material-symbols--filled"></span>`;
83+
}
84+
85+
/**
86+
* Generates the HTML string for the default marker icon, rendered as a
87+
* solid coloured circle with a white icon.
88+
*
89+
* @param options - Marker configuration supplying colour and icon values.
90+
* @returns An HTML string representing the default marker icon.
91+
*/
92+
private getMarkerContent(options: MarkerConfig): string {
93+
const [color, icon] = this.getColorAndIcon(options);
94+
95+
return `<span style="background-color: ${color}; border: solid 2px ${color}; color: white; font-size: 20px; padding: 4px; border-radius: 50%;" data-material-symbol="${icon}" class="material-symbols material-symbols-rounded material-symbols-sharp material-symbols-outlined material-symbols--filled"></span>`;
96+
}
97+
98+
/**
99+
* Extracts the colour and icon name from the marker options, falling back
100+
* to the default colour (#E04A39) and icon (location_on) when not provided.
101+
*
102+
* @param options - Marker configuration that may contain colour and icon overrides.
103+
* @returns A tuple of [color, icon] strings.
104+
*/
105+
private getColorAndIcon(options: MarkerConfig): [string, string] {
106+
const color = options.color ?? '#E04A39';
107+
const icon = options.icon ?? 'location_on';
108+
109+
return [color, icon];
110+
}
111+
}
112+
113+
export default Marker;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { CreateAttribution, CreateMap, CreateMarker, CreatePopup, CreateTileLayer, type MapInterface, TilesHelper } from '@helsingborg-stad/openstreetmap';
2+
import Marker from './marker';
3+
import type { OpenstreetmapArgs } from './openstreetmapInterface';
4+
5+
class Openstreetmap {
6+
private map!: MapInterface;
7+
8+
/**
9+
* Creates an Openstreetmap instance and initializes the map.
10+
*
11+
* @param openstreetmapArgs - Verified configuration for the map.
12+
*/
13+
constructor(private openstreetmapArgs: OpenstreetmapArgs) {
14+
this.createMap();
15+
}
16+
17+
/**
18+
* Creates the map instance, attaches the tile layer, attribution, and
19+
* renders all configured markers onto the map.
20+
*/
21+
private createMap() {
22+
this.map = new CreateMap({
23+
id: this.openstreetmapArgs.id,
24+
center: { lat: this.openstreetmapArgs.lat, lng: this.openstreetmapArgs.lng },
25+
zoom: this.openstreetmapArgs.zoom,
26+
}).create();
27+
28+
const { url, attribution } = new TilesHelper().getDefaultTiles(this.openstreetmapArgs.style);
29+
const markerCreator = new Marker(new CreateMarker(), new CreatePopup());
30+
new CreateTileLayer().create().setUrl(url).addTo(this.map);
31+
new CreateAttribution().create().setPrefix(attribution).addTo(this.map);
32+
33+
for (const markerConfig of this.openstreetmapArgs.markers) {
34+
const marker = markerCreator.create(markerConfig);
35+
marker.addTo(this.map);
36+
}
37+
}
38+
}
39+
40+
export default Openstreetmap;

0 commit comments

Comments
 (0)