|
| 1 | +--- |
| 2 | +import BaseLayout from '../layouts/BaseLayout.astro'; |
| 3 | +import releases from '../../assets/data/releases.json'; |
| 4 | +
|
| 5 | +const release = releases.current_release; |
| 6 | +
|
| 7 | +const breadcrumb = { |
| 8 | + '@context': 'https://schema.org', |
| 9 | + '@type': 'BreadcrumbList', |
| 10 | + itemListElement: [ |
| 11 | + { '@type': 'ListItem', position: 1, name: 'Home', item: 'https://geoiplocations.com/' }, |
| 12 | + { '@type': 'ListItem', position: 2, name: 'Density map', item: 'https://geoiplocations.com/prefix-density.html' }, |
| 13 | + ], |
| 14 | +}; |
| 15 | +--- |
| 16 | +<BaseLayout |
| 17 | + title="IPv4 Density Map — Where IP Space Concentrates | ProdIPData" |
| 18 | + description="An interactive hex-bin density map of the published IPv4 /24 space for the current GeoIP Locations release — see where address space concentrates worldwide, rendered in your browser." |
| 19 | + ogTitle="IPv4 Density Map" |
| 20 | + canonicalPath="/prefix-density.html" |
| 21 | + page="prefix-density" |
| 22 | + navPage="prefix-density" |
| 23 | +> |
| 24 | + <script slot="head" type="application/ld+json" is:inline set:html={JSON.stringify(breadcrumb)} /> |
| 25 | + |
| 26 | + <main class="container density"> |
| 27 | + <section class="density-hero"> |
| 28 | + <h1>Where IPv4 lives</h1> |
| 29 | + <p class="density-sub"> |
| 30 | + A hex-bin density view of the published <code>/24</code> space for the <strong>{release}</strong> |
| 31 | + release — brighter cells hold more address blocks. Aggregated and rendered entirely in your |
| 32 | + browser; drag the hex size, tilt to 3D, and hover any cell for its count. |
| 33 | + </p> |
| 34 | + </section> |
| 35 | + |
| 36 | + <div class="dmap-wrap"> |
| 37 | + <div id="dmap"></div> |
| 38 | + <div id="dpanel"> |
| 39 | + <div class="dp-title">Density</div> |
| 40 | + <div class="dp-meta" id="dmeta">loading…</div> |
| 41 | + <label>Hex radius: <span id="rOut">70</span> km</label> |
| 42 | + <input id="r" type="range" min="20" max="240" value="70" step="5" /> |
| 43 | + <label class="dp-check"><input type="checkbox" id="ext" /> 3D extrude</label> |
| 44 | + <div class="dp-legend-label">sparse → dense</div> |
| 45 | + <div class="dp-legend" id="leg"></div> |
| 46 | + </div> |
| 47 | + <div id="dtip"></div> |
| 48 | + </div> |
| 49 | + |
| 50 | + <p class="density-note muted"> |
| 51 | + One record per geolocated <code>/24</code>, binned into hexagons by deck.gl. Open-source, no map |
| 52 | + tiles, no tracking. Looking for a specific block? Use <a href={`./prefix-lookup.html`}>Prefix Lookup</a>. |
| 53 | + </p> |
| 54 | + </main> |
| 55 | + |
| 56 | + <div id="dcfg" data-release={release} data-base={import.meta.env.BASE_URL} hidden></div> |
| 57 | + |
| 58 | + <style is:inline> |
| 59 | + .density { padding: 2.2rem 0 3.5rem; max-width: 1100px; } |
| 60 | + .density-hero h1 { margin: 0 0 .4rem; } |
| 61 | + .density-sub { color: var(--muted); line-height: 1.7; margin: 0 0 1.4rem; max-width: 820px; } |
| 62 | + .dmap-wrap { position: relative; width: 100%; height: 600px; border-radius: 18px; overflow: hidden; |
| 63 | + border: 1px solid var(--line); background: #070d1c; } |
| 64 | + #dmap { position: absolute; inset: 0; } |
| 65 | + #dpanel { position: absolute; top: 14px; left: 14px; z-index: 2; width: 210px; |
| 66 | + background: rgba(11,21,48,.9); border: 1px solid rgba(255,255,255,.12); border-radius: 12px; |
| 67 | + padding: 13px 15px; color: var(--text); font-size: 13px; backdrop-filter: blur(6px); } |
| 68 | + #dpanel .dp-title { font-size: 14px; font-weight: 500; } |
| 69 | + #dpanel .dp-meta { color: var(--muted); font-size: 12px; margin: 1px 0 8px; } |
| 70 | + #dpanel label { display: block; margin: 9px 0 3px; color: var(--muted); } |
| 71 | + #dpanel input[type=range] { width: 100%; } |
| 72 | + .dp-check { display: flex; align-items: center; gap: 7px; margin-top: 8px; color: var(--muted); } |
| 73 | + .dp-check input { width: auto; } |
| 74 | + .dp-legend-label { color: var(--muted); font-size: 11px; margin-top: 10px; } |
| 75 | + .dp-legend { display: flex; height: 10px; border-radius: 5px; overflow: hidden; margin-top: 4px; } |
| 76 | + .dp-legend i { flex: 1; } |
| 77 | + #dtip { position: absolute; z-index: 3; pointer-events: none; background: rgba(0,0,0,.82); color: #fff; |
| 78 | + font-size: 12px; padding: 4px 8px; border-radius: 6px; display: none; } |
| 79 | + .density-note { margin-top: 1.4rem; font-size: .9rem; } |
| 80 | + </style> |
| 81 | + |
| 82 | + <script is:inline src="https://cdn.jsdelivr.net/npm/deck.gl@9/dist.min.js"></script> |
| 83 | + <script is:inline src="https://cdn.jsdelivr.net/npm/topojson-client@3/dist/topojson-client.min.js"></script> |
| 84 | + <script is:inline> |
| 85 | + (function () { |
| 86 | + var cfg = document.getElementById('dcfg').dataset; |
| 87 | + var base = cfg.base.endsWith('/') ? cfg.base : cfg.base + '/'; |
| 88 | + var release = cfg.release; |
| 89 | + var RAMP = [[12,44,92],[14,116,144],[6,182,212],[250,204,21],[249,115,22],[239,68,68]]; |
| 90 | + document.getElementById('leg').innerHTML = RAMP.map(function (c) { return '<i style="background:rgb(' + c + ')"></i>'; }).join(''); |
| 91 | + var tip = document.getElementById('dtip'); |
| 92 | + var points = [], world = null; |
| 93 | + |
| 94 | + var deckgl = new deck.DeckGL({ |
| 95 | + container: 'dmap', |
| 96 | + initialViewState: { longitude: 5, latitude: 25, zoom: 1.15, pitch: 0, bearing: 0 }, |
| 97 | + controller: true, |
| 98 | + parameters: { clearColor: [0.027, 0.05, 0.11, 1] } |
| 99 | + }); |
| 100 | + |
| 101 | + function build() { |
| 102 | + var radius = +document.getElementById('r').value * 1000; |
| 103 | + var extruded = document.getElementById('ext').checked; |
| 104 | + var layers = []; |
| 105 | + if (world) layers.push(new deck.GeoJsonLayer({ |
| 106 | + id: 'world', data: world, stroked: true, filled: true, |
| 107 | + getFillColor: [20, 35, 64], getLineColor: [255, 255, 255, 30], lineWidthMinPixels: 0.4 |
| 108 | + })); |
| 109 | + layers.push(new deck.HexagonLayer({ |
| 110 | + id: 'hex', data: points, |
| 111 | + getPosition: function (d) { return [d[0], d[1]]; }, |
| 112 | + getColorWeight: function (d) { return d[2]; }, getElevationWeight: function (d) { return d[2]; }, |
| 113 | + colorAggregation: 'SUM', elevationAggregation: 'SUM', |
| 114 | + radius: radius, coverage: 0.92, opacity: 0.85, |
| 115 | + extruded: extruded, elevationScale: extruded ? 25 : 0, |
| 116 | + colorRange: RAMP, pickable: true, |
| 117 | + updateTriggers: { getColorWeight: radius }, |
| 118 | + onHover: function (info) { |
| 119 | + if (info.object) { |
| 120 | + var n = info.object.colorValue != null ? info.object.colorValue : (info.object.elevationValue || 0); |
| 121 | + tip.style.display = 'block'; |
| 122 | + tip.style.left = (info.x + 12) + 'px'; |
| 123 | + tip.style.top = (info.y + 12) + 'px'; |
| 124 | + tip.textContent = n.toLocaleString() + ' /24s in this hex'; |
| 125 | + } else { tip.style.display = 'none'; } |
| 126 | + } |
| 127 | + })); |
| 128 | + deckgl.setProps({ layers: layers }); |
| 129 | + } |
| 130 | + |
| 131 | + document.getElementById('r').addEventListener('input', function (e) { |
| 132 | + document.getElementById('rOut').textContent = e.target.value; build(); |
| 133 | + }); |
| 134 | + document.getElementById('ext').addEventListener('change', build); |
| 135 | + |
| 136 | + fetch('https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json') |
| 137 | + .then(function (r) { return r.json(); }) |
| 138 | + .then(function (w) { world = topojson.feature(w, w.objects.countries); if (points.length) build(); }); |
| 139 | + |
| 140 | + fetch(base + 'assets/data/density-' + release + '.json') |
| 141 | + .then(function (r) { return r.json(); }) |
| 142 | + .then(function (j) { |
| 143 | + points = j.points; |
| 144 | + document.getElementById('dmeta').textContent = points.length.toLocaleString() + ' points · ' + j.release; |
| 145 | + build(); |
| 146 | + }) |
| 147 | + .catch(function () { document.getElementById('dmeta').textContent = 'density data not published yet'; }); |
| 148 | + })(); |
| 149 | + </script> |
| 150 | +</BaseLayout> |
0 commit comments