Small fixes
This commit is contained in:
parent
5c59bf3bc8
commit
b3a4ce6fdc
2
ipmap.py
2
ipmap.py
|
@ -180,7 +180,7 @@ def generate_tiles(parquet_path: Path, tiles_dir: Path, *, tile_size = default_t
|
||||||
while True:
|
while True:
|
||||||
for colormap in colormaps:
|
for colormap in colormaps:
|
||||||
if generate_density:
|
if generate_density:
|
||||||
generate_images(colormap, "density", "count", possible_overlaps)
|
generate_images(colormap, "density", "count", 256 if possible_overlaps == 1 else possible_overlaps)
|
||||||
if generate_rtt:
|
if generate_rtt:
|
||||||
generate_images(colormap, "rtt", "rtt_us", df.get_column("rtt_us").std() / tiles_per_side.bit_length())
|
generate_images(colormap, "rtt", "rtt_us", df.get_column("rtt_us").std() / tiles_per_side.bit_length())
|
||||||
if tiles_per_side == 1:
|
if tiles_per_side == 1:
|
||||||
|
|
|
@ -50,32 +50,57 @@
|
||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
.maplibregl-ctrl-top-right {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.maplibregl-ctrl-top-right .custom-control {
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
.maplibregl-ctrl button.maplibregl-ctrl-subnets .maplibregl-ctrl-icon {
|
||||||
|
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='29' height='29' stroke='%23333' stroke-width='1.5' fill='none' viewBox='0 0 29 29'%3E%3Cpath d='m 6 6 v 17 h 17 v -17 h -17 z m 0 8.5 h 17 m -8.5 -8.5 v 17'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
.custom-control {
|
.custom-control {
|
||||||
display: inline;
|
|
||||||
float: unset !important;
|
|
||||||
vertical-align: top;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
.custom-control summary {
|
details.custom-control > summary {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0.2rem 0.5rem;
|
padding: 0 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 29px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.custom-control[open] summary {
|
details.custom-control[open] > summary {
|
||||||
border-bottom: 2px solid black;
|
border-bottom: 2px solid black;
|
||||||
}
|
}
|
||||||
.custom-control h3 {
|
details.custom-control > summary::marker {
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
details.custom-control > summary {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
details.custom-control > summary::-webkit-details-marker {
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
details.custom-control > summary h3 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
vertical-align: middle;
|
|
||||||
display: inline;
|
display: inline;
|
||||||
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
.custom-control button {
|
details.custom-control > .button-container {
|
||||||
|
max-height: 12rem;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
details.custom-control > .button-container button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0 0.5rem;
|
padding: 0 0.5rem;
|
||||||
color: black;
|
|
||||||
}
|
}
|
||||||
.custom-control button.active {
|
.maplibregl-ctrl button {
|
||||||
|
color: black;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.maplibregl-ctrl button.active {
|
||||||
background-color: rgb(0 0 0/15%);
|
background-color: rgb(0 0 0/15%);
|
||||||
}
|
}
|
||||||
.maplibregl-ctrl button:not(:disabled):hover {
|
.maplibregl-ctrl button:not(:disabled):hover {
|
||||||
|
@ -84,6 +109,13 @@
|
||||||
.maplibregl-ctrl button:not(:disabled):active {
|
.maplibregl-ctrl button:not(:disabled):active {
|
||||||
background-color: rgb(0 0 0/15%);
|
background-color: rgb(0 0 0/15%);
|
||||||
}
|
}
|
||||||
|
.maplibregl-ctrl p {
|
||||||
|
margin: 0.25rem 0.5rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.maplibregl-ctrl p:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -142,8 +174,10 @@
|
||||||
return coord
|
return coord
|
||||||
}
|
}
|
||||||
|
|
||||||
const ipToString = ip => `${ip >> 24 & 0xFF}.${ip >> 16 & 0xFF}.${ip >> 8 & 0xFF}.${ip >> 0 & 0xFF}`
|
const ipToString = (ip, subnet = 32) => {
|
||||||
const ipToRange = (ip, subnet) => `${ipToString((ip >> (32 - subnet)) << (32 - subnet))}/${subnet}`
|
const x = (ip >> (32 - subnet)) << (32 - subnet)
|
||||||
|
return `${x >> 24 & 0xFF}.${x >> 16 & 0xFF}.${x >> 8 & 0xFF}.${x >> 0 & 0xFF}`
|
||||||
|
}
|
||||||
|
|
||||||
const rangeToCoords = range => {
|
const rangeToCoords = range => {
|
||||||
const [a, b, c, d, subnet] = range.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)\/(\d+)$/).slice(1).map(Number)
|
const [a, b, c, d, subnet] = range.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)\/(\d+)$/).slice(1).map(Number)
|
||||||
|
@ -191,34 +225,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const map = new maplibregl.Map({
|
const defaultVariant = "density"
|
||||||
container: "map",
|
const defaultColormap = "jet"
|
||||||
attributionControl: false,
|
|
||||||
renderWorldCopies: false,
|
|
||||||
doubleClickZoom: false,
|
|
||||||
dragRotate: false,
|
|
||||||
pitchWithRotate: false,
|
|
||||||
touchPitch: false,
|
|
||||||
style: {
|
|
||||||
version: 8,
|
|
||||||
sources: {},
|
|
||||||
layers: []
|
|
||||||
},
|
|
||||||
center: [0, 0],
|
|
||||||
minZoom: -2,
|
|
||||||
maxZoom: 12,
|
|
||||||
zoom: -2
|
|
||||||
})
|
|
||||||
map.painter.context.extTextureFilterAnisotropic = undefined
|
|
||||||
map.touchZoomRotate.disableRotation()
|
|
||||||
|
|
||||||
const tileSize = 256
|
const tileSize = 256
|
||||||
const tilesDir = "assets/tiles"
|
const tilesDir = "assets/tiles"
|
||||||
const tilesId = "ipmap-tiles"
|
const tilesId = "ipmap-tiles"
|
||||||
const privateRangesId = "private-ranges"
|
const privateRangesId = "private-ranges"
|
||||||
const subnetsId = "subnets"
|
const subnetsId = "subnets"
|
||||||
const dataP = fetch(`${tilesDir}/tiles.json`, { cache: "no-store" }).then(res => res.json())
|
|
||||||
const privateRangePatternP = map.loadImage("assets/private-range-pattern.png")
|
|
||||||
|
|
||||||
const canvas = document.createElement("canvas")
|
const canvas = document.createElement("canvas")
|
||||||
const canvasScale = window.devicePixelRatio
|
const canvasScale = window.devicePixelRatio
|
||||||
|
@ -230,13 +243,12 @@
|
||||||
ctx.lineWidth = 3
|
ctx.lineWidth = 3
|
||||||
ctx.font = "bold 20px sans-serif"
|
ctx.font = "bold 20px sans-serif"
|
||||||
maplibregl.addProtocol(subnetsId, async (params, abortController) => {
|
maplibregl.addProtocol(subnetsId, async (params, abortController) => {
|
||||||
console.log(params.url)
|
|
||||||
const [z, y, x] = params.url.split("://")[1].split("/")
|
const [z, y, x] = params.url.split("://")[1].split("/")
|
||||||
const width = 2 ** z
|
const width = 2 ** z
|
||||||
const subnet = 2 * z
|
const subnet = 2 * z
|
||||||
const hy = Math.floor(0x10000 * (y / width))
|
const hy = Math.floor(0x10000 * (y / width))
|
||||||
const hx = Math.floor(0x10000 * (x / width))
|
const hx = Math.floor(0x10000 * (x / width))
|
||||||
const text = ipToRange(coordsToHilbert({ x: hx, y: hy }), subnet)
|
const text = `${ipToString(coordsToHilbert({ x: hx, y: hy }), subnet)}/${subnet}`
|
||||||
const metrics = ctx.measureText(text)
|
const metrics = ctx.measureText(text)
|
||||||
const cy = tileSize / 2 + metrics.actualBoundingBoxAscent / 2
|
const cy = tileSize / 2 + metrics.actualBoundingBoxAscent / 2
|
||||||
const cx = tileSize / 2 - metrics.actualBoundingBoxRight / 2
|
const cx = tileSize / 2 - metrics.actualBoundingBoxRight / 2
|
||||||
|
@ -254,23 +266,51 @@
|
||||||
return { data: await new Promise((res, rej) => canvas.toBlob(b => b.arrayBuffer().then(res))) }
|
return { data: await new Promise((res, rej) => canvas.toBlob(b => b.arrayBuffer().then(res))) }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const map = new maplibregl.Map({
|
||||||
|
container: "map",
|
||||||
|
attributionControl: false,
|
||||||
|
renderWorldCopies: false,
|
||||||
|
doubleClickZoom: false,
|
||||||
|
dragRotate: false,
|
||||||
|
pitchWithRotate: false,
|
||||||
|
touchPitch: false,
|
||||||
|
style: {
|
||||||
|
version: 8,
|
||||||
|
sources: {},
|
||||||
|
layers: []
|
||||||
|
},
|
||||||
|
center: [0, 0],
|
||||||
|
minZoom: -2,
|
||||||
|
maxZoom: 11.499,
|
||||||
|
zoom: -2
|
||||||
|
})
|
||||||
|
map.painter.context.extTextureFilterAnisotropic = undefined
|
||||||
|
map.touchZoomRotate.disableRotation()
|
||||||
|
|
||||||
|
const dataP = fetch(`${tilesDir}/tiles.json`, { cache: "no-store" }).then(res => res.json())
|
||||||
|
const privateRangePatternP = map.loadImage("assets/private-range-pattern.png")
|
||||||
|
|
||||||
map.once("style.load", async () => {
|
map.once("style.load", async () => {
|
||||||
const data = await dataP
|
const data = await dataP
|
||||||
const flatData = Object.entries(data)
|
|
||||||
.sort(([a], [b]) => a.localeCompare(b))
|
let curDate, curVariant, curColormap
|
||||||
.flatMap(([date, variantData]) =>
|
const dates = Object.keys(data).sort()
|
||||||
Object.entries(variantData)
|
curDate = dates.find(date => {
|
||||||
.sort(([a], [b]) => a.localeCompare(b))
|
const variantEntries = Object.entries(data[date])
|
||||||
.flatMap(([variant, colormaps]) => colormaps
|
if (variantEntries.length === 0) return false
|
||||||
.sort((a, b) => a.localeCompare(b))
|
const variantEntry = variantEntries.find(([variant, colormaps]) => variant === defaultVariant && colormaps.length > 0) ??
|
||||||
.flatMap(colormap => ({ date, variant, colormap }))))
|
variantEntries.sort(([a], [b]) => a.localeCompare(b)).find(([_, colormaps]) => colormaps.length > 0)
|
||||||
if (flatData.length === 0) {
|
if (!variantEntry) return false
|
||||||
console.log("no data found")
|
const [variant, colormaps] = variantEntry
|
||||||
|
curVariant = variant
|
||||||
|
curColormap = colormaps.find(colormap => colormap === defaultColormap) ?? colormaps.sort()[0]
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
if (!curDate) {
|
||||||
|
console.error("no data found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let { date: curDate, variant: curVariant, colormap: curColormap } = flatData[flatData.length - 1]
|
|
||||||
|
|
||||||
map.addSource(tilesId, {
|
map.addSource(tilesId, {
|
||||||
type: "raster",
|
type: "raster",
|
||||||
tiles: [`${tilesDir}/${curDate}/${curVariant}/${curColormap}/{z}/{y}/{x}.png`],
|
tiles: [`${tilesDir}/${curDate}/${curVariant}/${curColormap}/{z}/{y}/{x}.png`],
|
||||||
|
@ -334,10 +374,13 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let subnetsVisible = true
|
||||||
map.addSource(subnetsId, {
|
map.addSource(subnetsId, {
|
||||||
type: "raster",
|
type: "raster",
|
||||||
tiles: [`${subnetsId}://{z}/{y}/{x}`],
|
tiles: [`${subnetsId}://{z}/{y}/{x}`],
|
||||||
tileSize,
|
tileSize,
|
||||||
|
minzoom: 0,
|
||||||
|
maxzoom: 12,
|
||||||
})
|
})
|
||||||
map.addLayer({
|
map.addLayer({
|
||||||
id: subnetsId,
|
id: subnetsId,
|
||||||
|
@ -348,34 +391,41 @@
|
||||||
const setStyle = (date, variant, colormap) => {
|
const setStyle = (date, variant, colormap) => {
|
||||||
if (date === curDate && variant === curVariant && colormap === curColormap || !data[date]?.[variant]?.includes(colormap))
|
if (date === curDate && variant === curVariant && colormap === curColormap || !data[date]?.[variant]?.includes(colormap))
|
||||||
return false
|
return false
|
||||||
map.getSource(tilesId)?.setTiles([`${tilesDir}/${date}/${variant}/${colormap}/{z}/{y}/{x}.png`])
|
const source = map.getSource(tilesId)
|
||||||
|
if (!source) return false
|
||||||
|
source.setTiles([`${tilesDir}/${date}/${variant}/${colormap}/{z}/{y}/{x}.png`])
|
||||||
curDate = date
|
curDate = date
|
||||||
curVariant = variant
|
curVariant = variant
|
||||||
curColormap = colormap
|
curColormap = colormap
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const customControl = (name) => {
|
const addControl = (elem, side) => map.addControl({
|
||||||
|
onAdd: () => elem,
|
||||||
|
onRemove: () => elem.parentNode.removeChild(elem)
|
||||||
|
}, side)
|
||||||
|
|
||||||
|
const detailsControl = (name) => {
|
||||||
const container = document.createElement("details")
|
const container = document.createElement("details")
|
||||||
container.className = "maplibregl-ctrl maplibregl-ctrl-group custom-control"
|
container.className = "maplibregl-ctrl maplibregl-ctrl-group custom-control"
|
||||||
const header = document.createElement("h3")
|
const header = document.createElement("h3")
|
||||||
header.textContent = name
|
header.textContent = name
|
||||||
const summary = document.createElement("summary")
|
const summary = document.createElement("summary")
|
||||||
summary.replaceChildren(header)
|
summary.replaceChildren(header)
|
||||||
container.replaceChildren(summary)
|
const buttonContainer = document.createElement("div")
|
||||||
const setButtons = (buttons) => container.replaceChildren(summary, ...buttons.map(({ button }) => button))
|
buttonContainer.className = "button-container"
|
||||||
const addControl = () => map.addControl({
|
container.replaceChildren(summary, buttonContainer)
|
||||||
onAdd: () => container,
|
return {
|
||||||
onRemove: () => container.parentNode.removeChild(container)
|
container,
|
||||||
}, "top-right")
|
setButtons: buttons => buttonContainer.replaceChildren(...buttons.map(({ button }) => button)),
|
||||||
return { container, setButtons, addControl }
|
addControl: (side = "top-right") => addControl(container, side)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const dateControl = customControl("Date")
|
const dateControl = detailsControl("Date")
|
||||||
const variantControl = customControl("Variant")
|
const variantControl = detailsControl("Variant")
|
||||||
const colormapControl = customControl("Colormap")
|
const colormapControl = detailsControl("Colormap")
|
||||||
|
|
||||||
const dates = Object.keys(data).sort()
|
|
||||||
const dateButtons = dates.map(date => {
|
const dateButtons = dates.map(date => {
|
||||||
const button = document.createElement("button")
|
const button = document.createElement("button")
|
||||||
button.textContent = date
|
button.textContent = date
|
||||||
|
@ -421,33 +471,55 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
renderControls()
|
renderControls()
|
||||||
|
|
||||||
|
const subnetsControl = document.createElement("div")
|
||||||
|
subnetsControl.className = "maplibregl-ctrl maplibregl-ctrl-group custom-control"
|
||||||
|
const subnetsButton = document.createElement("button")
|
||||||
|
subnetsButton.className = "maplibregl-ctrl-subnets active"
|
||||||
|
const subnetsIcon = document.createElement("span")
|
||||||
|
subnetsIcon.className = "maplibregl-ctrl-icon"
|
||||||
|
subnetsButton.replaceChildren(subnetsIcon)
|
||||||
|
subnetsButton.addEventListener("click", () => {
|
||||||
|
subnetsVisible = !subnetsVisible
|
||||||
|
subnetsButton.classList.toggle("active")
|
||||||
|
map.setLayoutProperty(subnetsId, "visibility", subnetsVisible ? "visible" : "none")
|
||||||
|
})
|
||||||
|
subnetsControl.replaceChildren(subnetsButton)
|
||||||
|
addControl(subnetsControl, "top-right")
|
||||||
|
|
||||||
dateControl.addControl()
|
dateControl.addControl()
|
||||||
variantControl.addControl()
|
variantControl.addControl()
|
||||||
colormapControl.addControl()
|
colormapControl.addControl()
|
||||||
})
|
|
||||||
|
|
||||||
map.addControl(new maplibregl.NavigationControl({ showCompass: false }), "top-left")
|
const getIPAtMouse = (e, features, link) => {
|
||||||
|
const { x, y } = maplibregl.MercatorCoordinate.fromLngLat(e.lngLat, 0)
|
||||||
|
const ip = coordsToHilbert({ x: Math.floor(0x10000 * x), y: Math.floor(0x10000 * y) })
|
||||||
|
const subnet = Math.min(32, Math.round(map.getZoom()) * 2 + 18)
|
||||||
|
const ipStr = ipToString(ip, subnet)
|
||||||
|
const props = features && map.queryRenderedFeatures(e.point).find(f => f.layer.id === privateRangesId)?.properties
|
||||||
|
const propsText = props ? `<br>Part of private range ${props.range}<br>Used for ${props.description}` : ""
|
||||||
|
const name = subnet < 32 ? "Range" : "IP"
|
||||||
|
const ipText = subnet < 32 ? `${ipStr}/${subnet}` : ipStr
|
||||||
|
const ipLink = link ? `<a href="https://bgp.tools/prefix/${ipStr}" target="_blank">${ipText}</a>` : ipText
|
||||||
|
return `${name}: ${ipLink}${propsText}`
|
||||||
|
}
|
||||||
|
|
||||||
let curPopup
|
const hoverTextControl = document.createElement("div")
|
||||||
map.on("click", e => {
|
hoverTextControl.className = "maplibregl-ctrl maplibregl-ctrl-group"
|
||||||
const { x, y } = maplibregl.MercatorCoordinate.fromLngLat(e.lngLat, 0)
|
const hoverTextP = document.createElement("p")
|
||||||
const ip = coordsToHilbert({ x: Math.floor(0x10000 * x), y: Math.floor(0x10000 * y) })
|
hoverTextControl.replaceChildren(hoverTextP)
|
||||||
const subnet = Math.min(32, Math.round(map.getZoom()) * 2 + 18)
|
map.on("mousemove", e => hoverTextP.textContent = getIPAtMouse(e, false, false))
|
||||||
const props = map.queryRenderedFeatures(e.point).find(f => f.layer.id === privateRangesId)?.properties
|
addControl(hoverTextControl, "bottom-left")
|
||||||
const propsText = props ? `<br>Part of private range ${props.range}<br>Used for ${props.description}` : ""
|
|
||||||
const text = subnet < 32 ?
|
|
||||||
`Range: ${ipToRange(ip, subnet)}${propsText}` :
|
|
||||||
`IP: ${ipToString(ip)}${propsText}`
|
|
||||||
curPopup = new maplibregl.Popup()
|
|
||||||
.setHTML(text)
|
|
||||||
.setLngLat(e.lngLat)
|
|
||||||
.addTo(map)
|
|
||||||
})
|
|
||||||
|
|
||||||
const main = document.getElementsByTagName("main")[0]
|
map.addControl(new maplibregl.NavigationControl({ showCompass: false }), "top-left")
|
||||||
main.addEventListener("click", e => {
|
|
||||||
if (e.target === main && curPopup) curPopup.remove()
|
let curPopup
|
||||||
|
map.on("click", e => curPopup = new maplibregl.Popup().setHTML(getIPAtMouse(e, true, true)).setLngLat(e.lngLat).addTo(map))
|
||||||
|
|
||||||
|
const main = document.getElementsByTagName("main")[0]
|
||||||
|
main.addEventListener("click", e => {
|
||||||
|
if (e.target === main && curPopup) curPopup.remove()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
Loading…
Reference in New Issue