PageSourceSearch

https://haiphen.io/components/featured/context-map.js?v=1

js haiphen.io collected 2026-10-01 12:02:16 UTC 4,949 bytes, 135 lines download raw bytes

1/**
2 * Context Panel — Map Tab
3 *
4 * Renders a Leaflet map showing infrastructure concentration
5 * for the selected entity. Lazy-loads Leaflet from CDN.
6 */
7(function () {
8  "use strict";
9
10  var API_URL = "https://api.haiphen.io/v1/prospect/geo/concentration";
11  var LEAFLET_JS  = "https://unpkg.com/[email protected]/dist/leaflet.js";
12  var LEAFLET_CSS = "https://unpkg.com/[email protected]/dist/leaflet.css";
13  var TILE_URL    = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
14  var TILE_ATTR   = '&copy; <a href="https://www.openstreetmap.org/copyright">OSM</a>';
15
16  var COLORS = { high: "#dc2626", medium: "#f59e0b", low: "#10b981" };
17
18  var _geoData = null;
19  var _leafletReady = false;
20  var _mapInstance = null;
21  var _currentCompany = null;
22
23  function esc(s) { var d = document.createElement("div"); d.textContent = s; return d.innerHTML; }
24
25  function loadLeaflet(cb) {
26    if (_leafletReady) return cb();
27    var link = document.createElement("link");
28    link.rel = "stylesheet"; link.href = LEAFLET_CSS;
29    document.head.appendChild(link);
30    var script = document.createElement("script");
31    script.src = LEAFLET_JS;
32    script.onload = function () { _leafletReady = true; cb(); };
33    script.onerror = function () { cb(new Error("Leaflet load failed")); };
34    document.head.appendChild(script);
35  }
36
37  function fetchGeo(cb) {
38    if (_geoData) return cb(null, _geoData);
39    var ctrl = new AbortController();
40    var timer = setTimeout(function () { ctrl.abort(); }, 10000);
41    fetch(API_URL, { credentials: "include", signal: ctrl.signal })
42      .then(function (r) { clearTimeout(timer); return r.ok ? r.json() : Promise.reject(r.status); })
43      .then(function (d) { _geoData = d.companies || []; cb(null, _geoData); })
44      .catch(function (e) { clearTimeout(timer); cb(e); });
45  }
46
47  function renderMap(pane, company) {
48    var mapEl = pane.querySelector(".hp-obs__map-container");
49    if (!mapEl) return;
50
51    if (_mapInstance) { _mapInstance.remove(); _mapInstance = null; }
52
53    _mapInstance = L.map(mapEl, { scrollWheelZoom: false }).setView([20, 0], 2);
54    L.tileLayer(TILE_URL, { attribution: TILE_ATTR, maxZoom: 18 }).addTo(_mapInstance);
55
56    var bounds = [];
57    for (var i = 0; i < company.regions.length; i++) {
58      var r = company.regions[i];
59      if (r.lat == null || r.lng == null) continue;
60
61      var radius = Math.max(6, Math.min(30, Math.sqrt(r.count) * 8));
62      var color = COLORS[company.concentration] || COLORS.low;
63
64      L.circleMarker([r.lat, r.lng], {
65        radius: radius, fillColor: color, color: "#fff", weight: 1, fillOpacity: 0.7,
66      }).addTo(_mapInstance).bindPopup(
67        "<strong>" + esc(r.city || "Unknown") + ", " + esc(r.country || "??") + "</strong><br>"
68        + r.count + " endpoints (" + (r.share * 100).toFixed(1) + "%)"
69      );
70      bounds.push([r.lat, r.lng]);
71    }
72
73    if (bounds.length > 1) {
74      _mapInstance.fitBounds(bounds, { padding: [30, 30] });
75    } else if (bounds.length === 1) {
76      _mapInstance.setView(bounds[0], 6);
77    }
78
79    setTimeout(function () { if (_mapInstance) _mapInstance.invalidateSize(); }, 100);
80  }
81
82  function render(pane, entity) {
83    if (!pane || !entity) return;
84
85    var companyName = (entity.company_name || "").toLowerCase();
86
87    // If same company, just invalidate map size
88    if (_currentCompany === companyName && _mapInstance) {
89      setTimeout(function () { _mapInstance.invalidateSize(); }, 50);
90      return;
91    }
92    _currentCompany = companyName;
93
94    pane.innerHTML = '<div class="hp-obs__map-container"></div>'
95      + '<div class="hp-obs__map-legend">Loading geographic data&hellip;</div>';
96
97    fetchGeo(function (err, companies) {
98      if (err || !companies) {
99        pane.querySelector(".hp-obs__map-legend").textContent = "Geographic data unavailable.";
100        return;
101      }
102
103      var match = null;
104      for (var i = 0; i < companies.length; i++) {
105        if (companies[i].group_key.toLowerCase() === companyName) {
106          match = companies[i]; break;
107        }
108      }
109
110      if (!match || !match.regions || !match.regions.length) {
111        pane.querySelector(".hp-obs__map-legend").textContent = "No infrastructure data for " + (entity.company_name || "this entity") + ".";
112        return;
113      }
114
115      var legend = pane.querySelector(".hp-obs__map-legend");
116      if (legend) {
117        legend.textContent = "HHI: " + match.hhi.toFixed(3) + " (" + match.concentration
118          + ") — " + match.total_infrastructure + " endpoints across "
119          + match.regions.length + " regions";
120      }
121
122      loadLeaflet(function (leafErr) {
123        if (leafErr) {
124          if (legend) legend.textContent = "Map library failed to load.";
125          return;
126        }
127        renderMap(pane, match);
128      });
129    });
130  }
131
132  window.HAIPHEN = window.HAIPHEN || {};
133  window.HAIPHEN.Featured = window.HAIPHEN.Featured || {};
134  window.HAIPHEN.Featured.ContextMap = { render: render };
135})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.