Backup before syncing server scripts
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import maplibregl from 'maplibre-gl';
|
||||
import 'maplibre-gl/dist/maplibre-gl.css';
|
||||
import { attachIconLoader } from '../utils/mapIcons';
|
||||
interface MapComponentProps {
|
||||
onMapLoad: (map: maplibregl.Map) => void;
|
||||
onMapClick?: (lat: number, lng: number) => void;
|
||||
@@ -59,7 +60,6 @@ const MapComponent: React.FC<MapComponentProps> = ({
|
||||
style: {
|
||||
version: 8,
|
||||
glyphs: 'https://cdn.protomaps.com/fonts/pbf/{fontstack}/{range}.pbf',
|
||||
sprite: 'https://demotiles.maplibre.org/styles/osm-bright-gl-style/sprite',
|
||||
sources: {
|
||||
'local-osm-polygons': {
|
||||
type: 'vector',
|
||||
@@ -390,6 +390,8 @@ const MapComponent: React.FC<MapComponentProps> = ({
|
||||
});
|
||||
|
||||
map.current = initialMap;
|
||||
// آيقونات الـ POI تُحمّل عند الطلب بدل sprite مستضاف
|
||||
attachIconLoader(initialMap);
|
||||
|
||||
// Request User Location
|
||||
if ('geolocation' in navigator) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import maplibregl from 'maplibre-gl';
|
||||
import 'maplibre-gl/dist/maplibre-gl.css';
|
||||
import { attachIconLoader } from '../utils/mapIcons';
|
||||
|
||||
/**
|
||||
* CompareView — side-by-side, pan/zoom-synced map comparison.
|
||||
@@ -55,6 +56,8 @@ const CompareView: React.FC = () => {
|
||||
// already point at the absolute Martin host), so load it relatively — never
|
||||
// prefixed with the tile host, which does not serve /style.json.
|
||||
const lMap = new maplibregl.Map({ container: leftDiv.current, style: '/style.json', center: start, zoom: startZoom, attributionControl: false });
|
||||
// آيقونات الـ POI تُحمّل عند الطلب — يجب الربط قبل أول رسم للطبقات
|
||||
attachIconLoader(lMap);
|
||||
const rMap = new maplibregl.Map({ container: rightDiv.current, style: rasterStyle(REFS['esri-sat']), center: start, zoom: startZoom, attributionControl: false });
|
||||
lMap.addControl(new maplibregl.NavigationControl({ showCompass: false }), 'top-left');
|
||||
rMap.addControl(new maplibregl.AttributionControl({ compact: true }), 'bottom-right');
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user