/** * אינטגרציה רשמית עם govmap (פורטל המפות הממשלתי). * * ע"פ התיעוד (api.govmap.gov.il/docs): טוענים את govmap.api.js מהשרת * שלהם (אסור לארח עותק מקומי), יוצרים מפה בתוך div עם טוקן API הרשום * לדומיין, ומציירים את שרטוטי ה-DXF עם displayGeometries — שמקבל * WKT ברשת ישראל (EPSG:2039), בדיוק מערכת הקואורדינטות של השרטוטים. * * מצב govmap מחליף את מפת ה-Leaflet (זה ווידג'ט מלא, לא שכבת אריחים), * ולכן דורש חיבור לאינטרנט; שאר האפליקציה ממשיכה לעבוד אופליין. */ (function (global) { "use strict"; var API_URL = "https://www.govmap.gov.il/govmap/api/govmap.api.js"; var CONFIG_KEY = "gis.govmap.cfg"; // טוקן ברירת מחדל — ציבורי וכבול לדומיין gis.167.233.115.205.sslip.io // (מאושר בפורטל govmap, 2026-07). ניתן לדריסה מפאנל ההגדרות. var DEFAULT_TOKEN = "96d10612-00a4-4697-b04f-30f20793e79d"; var CHUNK = 400; // גאומטריות לכל קריאת displayGeometries var MAX_PER_KIND = 4000; // תקרה לקובץ (מוצג toast אם נחתך) var MAX_TEXTS = 500; // רקעים נתמכים (מתוך תיעוד setBackground) var BACKGROUNDS = [ { id: "0", name: "רחובות ומבנים" }, { id: "1", name: "תצלום אוויר" }, { id: "2", name: "משולב (תצ\"א + רחובות)" }, { id: "9", name: "מפה טופוגרפית" }, ]; var state = { apiLoaded: false, created: false, creating: null, // Promise בזמן יצירה lastExtent: null, // extent אחרון ברשת ישראל (מאירועי המפה) names: {}, // fileId → שמות הגאומטריות שנדחפו (למחיקה) gpsOn: false, }; // ---------- הגדרות ---------- function config() { try { var c = JSON.parse(localStorage.getItem(CONFIG_KEY) || "null"); if (c && typeof c === "object") return { token: c.token || DEFAULT_TOKEN, background: c.background || "0" }; } catch (e) { /* הגדרות פגומות */ } return { token: DEFAULT_TOKEN, background: "0" }; } function saveConfig(cfg) { localStorage.setItem(CONFIG_KEY, JSON.stringify({ token: cfg.token || "", background: cfg.background || "0" })); } function hasToken() { return !!config().token; } // ---------- טעינת ה-API ויצירת המפה ---------- function loadApi() { if (state.apiLoaded && global.govmap) return Promise.resolve(); return new Promise(function (resolve, reject) { var done = false; var to = setTimeout(function () { if (!done) { done = true; reject(new Error("GOVMAP_API_LOAD")); } }, 15000); var s = document.createElement("script"); s.src = API_URL; s.onload = function () { if (done) return; done = true; clearTimeout(to); state.apiLoaded = true; resolve(); }; s.onerror = function () { if (done) return; done = true; clearTimeout(to); reject(new Error("GOVMAP_API_LOAD")); }; document.head.appendChild(s); }); } /** * יוצר את המפה (פעם אחת) בתוך divId. * centerItm: {x,y} ברשת ישראל; level: ‎0-10. */ function ensureMap(divId, centerItm, level) { if (state.created) return Promise.resolve(); if (state.creating) return state.creating; state.creating = new Promise(function (resolve, reject) { // ה-API המודרני: createMap מחזיר Promise שנפתר כשה-iframe מחובר. // אין onError, ו-onLoad ממתין ל-OnLoadTrigger שלא תמיד נענה — לכן // לא מעבירים אף callback ונסמכים על ה-Promise עצמו. var settings = { token: config().token, background: String(config().background || "0"), showXY: false, }; if (centerItm) settings.center = { x: centerItm[0], y: centerItm[1] }; if (level !== undefined) settings.level = level; var done = false; function ok() { if (done) return; done = true; state.created = true; state.creating = null; subscribeEvents(); resolve(); } function fail(e) { if (done) return; done = true; state.creating = null; reject(e instanceof Error ? e : new Error("GOVMAP_CREATE")); } try { Promise.resolve(global.govmap.createMap(divId, settings)).then(ok, function (e) { // דחייה לפני שה-iframe קם = כישלון אמיתי; אחרי שקם — מתעלמים var el = document.getElementById(divId); if (!(el && el.querySelector("iframe"))) fail(e); }); } catch (e) { fail(e); } // ה-Promise של createMap לא תמיד נפתר (באג פנימי בצד govmap; אומת // 2026-07: המפה עולה אבל ההבטחה נתקעת עם 'reading slice'). לכן // זיהוי הצלחה עצמאי: iframe חי בתוך ה-div → הצלחה אחרי חסד קצר // שמאפשר ל-handshake להסתיים. var probe = setInterval(function () { if (done) { clearInterval(probe); return; } var el = document.getElementById(divId); if (el && el.querySelector("iframe")) { clearInterval(probe); setTimeout(ok, 4000); } }, 500); setTimeout(function () { clearInterval(probe); fail(new Error("GOVMAP_TIMEOUT")); }, 30000); }); return state.creating; } function subscribeEvents() { try { global.govmap.onEvent(global.govmap.events.EXTENT_CHANGE).progress(function (e) { if (e && e.extent) state.lastExtent = e.extent; }); global.govmap.onEvent(global.govmap.events.PAN).progress(function (e) { if (e && e.extent) state.lastExtent = e.extent; }); if (global.GovMapEvents && global.GovMapEvents.onClick) { global.govmap.onEvent(global.govmap.events.CLICK).progress(function (e) { global.GovMapEvents.onClick(e); }); } } catch (err) { /* אירועים אופציונליים */ } } function setBackground(id) { var cfg = config(); cfg.background = String(id); saveConfig(cfg); if (state.created) { try { global.govmap.setBackground(parseInt(id, 10)); } catch (e) { /* לא קריטי */ } } } // ---------- המרות זום ---------- /** רזולוציית govmap לפי רמה (יחס 2 בין רמות; רמה 2 ≈ 132.29 מ'/פיקסל ע"פ התיעוד) */ function levelForLeafletZoom(z) { var groundRes = 156543.03392 * Math.cos(32 * Math.PI / 180) / Math.pow(2, z); var level = Math.round(Math.log(529.17 / groundRes) / Math.LN2); return Math.max(0, Math.min(10, level)); } function zoomTo(itmXY, level, marker) { if (!state.created) return; try { global.govmap.zoomToXY({ x: itmXY[0], y: itmXY[1], level: level === undefined ? 8 : level, marker: !!marker }); } catch (e) { /* לא קריטי */ } } // ---------- WKT ודחיפת שרטוטים ---------- function fmt(v) { return (Math.round(v * 100) / 100).toString(); } function lineWkt(pts) { return "LINESTRING(" + pts.map(function (p) { return fmt(p[0]) + " " + fmt(p[1]); }).join(",") + ")"; } function polygonWkt(pts) { var ring = pts.slice(); var f = ring[0], l = ring[ring.length - 1]; if (f[0] !== l[0] || f[1] !== l[1]) ring.push(f); return "POLYGON((" + ring.map(function (p) { return fmt(p[0]) + " " + fmt(p[1]); }).join(",") + "))"; } function hexToRgba(hex, alpha) { var h = (hex || "#ff3c3c").replace("#", ""); return [parseInt(h.slice(0, 2), 16) || 0, parseInt(h.slice(2, 4), 16) || 0, parseInt(h.slice(4, 6), 16) || 0, alpha]; } function chunkCall(kind, fileId, items, buildData) { // מפצל לקריאות של CHUNK, מחזיר promise; צובר שמות למחיקה עתידית var calls = []; for (var i = 0; i < items.length; i += CHUNK) { (function (slice, idx) { var name = "dxf_" + fileId + "_" + kind + "_" + idx; calls.push(function () { var data = buildData(slice, name); state.names[fileId].push(name); return Promise.resolve(global.govmap.displayGeometries(data)).catch(function () { /* קריאה בודדת נכשלה */ }); }); })(items.slice(i, i + CHUNK), i / CHUNK); } return calls; } /** * דוחף קובץ למפת govmap. toItm: פונקציה [x,y]→[E,N] ברשת ישראל. * opts: {showTexts: bool, darkBase: bool} */ function pushFile(file, toItm, opts) { if (!state.created) return Promise.resolve(); opts = opts || {}; clearFile(file.id); state.names[file.id] = []; var alpha = file.opacity !== undefined ? file.opacity : 1; var weight = file.weight || 2; var dark = opts.darkBase; var lines = [], polys = [], texts = [], truncated = false; file.entities.forEach(function (e) { var color = dark ? e.colorDark : e.color; if (e.type === "poly") { var pts; try { pts = e.pts.map(toItm); } catch (err) { return; } if (pts.length < 2) return; if (e.closed && pts.length >= 3) { if (polys.length < MAX_PER_KIND) polys.push({ wkt: polygonWkt(pts), color: color }); else truncated = true; } else { if (lines.length < MAX_PER_KIND) lines.push({ wkt: lineWkt(pts), color: color }); else truncated = true; } } else if (e.type === "point") { var p; try { p = toItm(e.pt); } catch (err) { return; } if (lines.length < MAX_PER_KIND) { // נקודה כקו-צלב קטן (חצי מטר) — נראה בכל זום סביר של עבודת שטח lines.push({ wkt: lineWkt([[p[0] - 0.5, p[1]], [p[0] + 0.5, p[1]]]), color: color }); } } else if (e.type === "text" && opts.showTexts !== false) { var t; try { t = toItm(e.pt); } catch (err) { return; } if (texts.length < MAX_TEXTS) texts.push({ x: t[0], y: t[1], label: e.text.split("\n")[0].slice(0, 60), color: color }); else truncated = true; } }); var calls = []; calls = calls.concat(chunkCall("ln", file.id, lines, function (slice, name) { return { wkts: slice.map(function (s) { return s.wkt; }), names: slice.map(function () { return name; }), geometryType: global.govmap.geometryType.POLYLINE, defaultSymbol: { color: hexToRgba(slice[0].color, alpha), width: weight }, symbols: slice.map(function (s) { return { color: hexToRgba(s.color, alpha), width: weight }; }), clearExisting: false, showBubble: false, data: { tooltips: slice.map(function () { return file.name; }) }, }; })); calls = calls.concat(chunkCall("pg", file.id, polys, function (slice, name) { return { wkts: slice.map(function (s) { return s.wkt; }), names: slice.map(function () { return name; }), geometryType: global.govmap.geometryType.POLYGON, defaultSymbol: { outlineColor: hexToRgba(slice[0].color, alpha), outlineWidth: weight, fillColor: [0, 0, 0, 0] }, symbols: slice.map(function (s) { return { outlineColor: hexToRgba(s.color, alpha), outlineWidth: weight, fillColor: [0, 0, 0, 0] }; }), clearExisting: false, showBubble: false, data: { tooltips: slice.map(function () { return file.name; }) }, }; })); if (texts.length) { calls = calls.concat(chunkCall("tx", file.id, texts, function (slice, name) { return { circleGeometries: slice.map(function (s) { return { x: s.x, y: s.y, radius: 0.15 }; }), names: slice.map(function () { return name; }), geometryType: global.govmap.geometryType.CIRCLE, defaultSymbol: { outlineColor: [0, 0, 0, 0], outlineWidth: 0, fillColor: [0, 0, 0, 0] }, symbols: [], clearExisting: false, showBubble: false, data: { labels: slice.map(function (s) { return s.label; }), fontLabel: { font: "Arial", fontSize: 12, fill: dark ? "#ffffff" : "#111111", stroke: dark ? "#000000" : "#ffffff" }, }, }; })); } // ריצה טורית — לא להציף את המפה var chain = Promise.resolve(); calls.forEach(function (c) { chain = chain.then(c); }); return chain.then(function () { return { truncated: truncated, counts: { lines: lines.length, polys: polys.length, texts: texts.length } }; }); } function clearFile(fileId) { var names = state.names[fileId]; if (names && names.length && state.created) { try { global.govmap.clearGeometriesByName(names); } catch (e) { /* אולי כבר נמחקו */ } } state.names[fileId] = []; } function clearAll() { Object.keys(state.names).forEach(clearFile); } // ---------- GPS ---------- function toggleGps() { if (!state.created) return false; try { if (state.gpsOn) { global.govmap.gpsOff(); state.gpsOn = false; } else { global.govmap.gpsOn(); state.gpsOn = true; } } catch (e) { return false; } return state.gpsOn; } global.GovMap = { BACKGROUNDS: BACKGROUNDS, config: config, saveConfig: saveConfig, hasToken: hasToken, loadApi: loadApi, ensureMap: ensureMap, setBackground: setBackground, levelForLeafletZoom: levelForLeafletZoom, zoomTo: zoomTo, pushFile: pushFile, clearFile: clearFile, clearAll: clearAll, toggleGps: toggleGps, isCreated: function () { return state.created; }, isGpsOn: function () { return state.gpsOn; }, lastExtent: function () { return state.lastExtent; }, }; })(window);