68 lines
2.8 KiB
TypeScript
68 lines
2.8 KiB
TypeScript
import type { Map as MlMap } from 'maplibre-gl';
|
|
|
|
/**
|
|
* تحميل آيقونات الـ POI عند الطلب من ملفات SVG في /icons.
|
|
*
|
|
* لماذا لا sprite: الستايل كان يشير إلى demotiles.maplibre.org (خادم MapLibre
|
|
* التجريبي). حين يفشل تحميله يرسم MapLibre نص التسمية بلا آيقونة وبصمت — فتظهر
|
|
* الخريطة بأسماء بلا رموز. البديل المعتاد هو بناء sprite sheet، لكنه يتطلب
|
|
* سلسلة أدوات (cairo/resvg) واستضافة ملف إضافي ومزامنته مع كل آيقونة جديدة.
|
|
*
|
|
* `styleimagemissing` ينطلق مرة واحدة لكل آيقونة تطلبها طبقة ولا يجدها المحرك،
|
|
* فنحمّلها وقتها من الـ SVG الموجود أصلاً في المستودع. لا خطوة بناء، ولا ملف
|
|
* يُستضاف، وإضافة آيقونة جديدة = إسقاط ملف SVG في المجلد فقط.
|
|
*
|
|
* ALIASES تسدّ الفجوة بين ما تطلبه طبقات الستايل وما هو متوفر فعلاً كملف.
|
|
*/
|
|
const ALIASES: Record<string, string> = {
|
|
rail: 'train',
|
|
college: 'tourist',
|
|
school: 'tourist',
|
|
cafe: 'cafe',
|
|
restaurant: 'restaurant',
|
|
};
|
|
|
|
const SIZE = 24;
|
|
|
|
export function attachIconLoader(map: MlMap): () => void {
|
|
const pending = new Set<string>();
|
|
|
|
const onMissing = (e: { id: string }) => {
|
|
const id = e.id;
|
|
// الحدث قد يتكرر لنفس الآيقونة قبل اكتمال التحميل غير المتزامن
|
|
if (!id || pending.has(id) || map.hasImage(id)) return;
|
|
pending.add(id);
|
|
|
|
const file = ALIASES[id] ?? id;
|
|
const img = new Image(SIZE, SIZE);
|
|
img.crossOrigin = 'anonymous';
|
|
|
|
img.onload = () => {
|
|
// الفحص مكرر عمداً: التحميل غير متزامن وقد يكون الستايل تبدّل أثناءه
|
|
if (!map.hasImage(id)) {
|
|
try {
|
|
map.addImage(id, img, { pixelRatio: 1 });
|
|
} catch {
|
|
/* الستايل تبدّل تحتنا — تُطلب مجدداً عند الحاجة */
|
|
}
|
|
}
|
|
pending.delete(id);
|
|
};
|
|
|
|
img.onerror = () => {
|
|
// بلا ملف مقابل: نضيف بكسلاً شفافاً حتى لا يعيد MapLibre إطلاق الحدث
|
|
// في كل إطار. النص يظهر بلا رمز، وهو أفضل من حلقة لا تنتهي.
|
|
if (!map.hasImage(id)) {
|
|
map.addImage(id, { width: 1, height: 1, data: new Uint8Array(4) });
|
|
}
|
|
pending.delete(id);
|
|
console.warn(`[mapIcons] لا يوجد /icons/${file}.svg للآيقونة "${id}"`);
|
|
};
|
|
|
|
img.src = `/icons/${file}.svg`;
|
|
};
|
|
|
|
map.on('styleimagemissing', onMissing);
|
|
return () => map.off('styleimagemissing', onMissing);
|
|
}
|