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:
|
||||
for colormap in colormaps:
|
||||
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:
|
||||
generate_images(colormap, "rtt", "rtt_us", df.get_column("rtt_us").std() / tiles_per_side.bit_length())
|
||||
if tiles_per_side == 1:
|
||||
|
|
|
@ -50,32 +50,57 @@
|
|||
font-size: 1.3rem;
|
||||
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 {
|
||||
display: inline;
|
||||
float: unset !important;
|
||||
vertical-align: top;
|
||||
user-select: none;
|
||||
}
|
||||
.custom-control summary {
|
||||
details.custom-control > summary {
|
||||
margin: 0;
|
||||
padding: 0.2rem 0.5rem;
|
||||
padding: 0 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 29px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.custom-control[open] summary {
|
||||
details.custom-control[open] > summary {
|
||||
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;
|
||||
padding: 0;
|
||||
vertical-align: middle;
|
||||
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%;
|
||||
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%);
|
||||
}
|
||||
.maplibregl-ctrl button:not(:disabled):hover {
|
||||
|
@ -84,6 +109,13 @@
|
|||
.maplibregl-ctrl button:not(:disabled):active {
|
||||
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>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -142,8 +174,10 @@
|
|||
return coord
|
||||
}
|
||||
|
||||
const ipToString = ip => `${ip >> 24 & 0xFF}.${ip >> 16 & 0xFF}.${ip >> 8 & 0xFF}.${ip >> 0 & 0xFF}`
|
||||
const ipToRange = (ip, subnet) => `${ipToString((ip >> (32 - subnet)) << (32 - subnet))}/${subnet}`
|
||||
const ipToString = (ip, subnet = 32) => {
|
||||
const x = (ip >> (32 - subnet)) << (32 - subnet)
|
||||
return `${x >> 24 & 0xFF}.${x >> 16 & 0xFF}.${x >> 8 & 0xFF}.${x >> 0 & 0xFF}`
|
||||
}
|
||||
|
||||
const rangeToCoords = range => {
|
||||
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({
|
||||
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: 12,
|
||||
zoom: -2
|
||||
})
|
||||
map.painter.context.extTextureFilterAnisotropic = undefined
|
||||
map.touchZoomRotate.disableRotation()
|
||||
|
||||
const defaultVariant = "density"
|
||||
const defaultColormap = "jet"
|
||||
const tileSize = 256
|
||||
const tilesDir = "assets/tiles"
|
||||
const tilesId = "ipmap-tiles"
|
||||
const privateRangesId = "private-ranges"
|
||||
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 canvasScale = window.devicePixelRatio
|
||||
|
@ -230,13 +243,12 @@
|
|||
ctx.lineWidth = 3
|
||||
ctx.font = "bold 20px sans-serif"
|
||||
maplibregl.addProtocol(subnetsId, async (params, abortController) => {
|
||||
console.log(params.url)
|
||||
const [z, y, x] = params.url.split("://")[1].split("/")
|
||||
const width = 2 ** z
|
||||
const subnet = 2 * z
|
||||
const hy = Math.floor(0x10000 * (y / 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 cy = tileSize / 2 + metrics.actualBoundingBoxAscent / 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))) }
|
||||
})
|
||||
|
||||
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 () => {
|
||||
const data = await dataP
|
||||
const flatData = Object.entries(data)
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
.flatMap(([date, variantData]) =>
|
||||
Object.entries(variantData)
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
.flatMap(([variant, colormaps]) => colormaps
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
.flatMap(colormap => ({ date, variant, colormap }))))
|
||||
if (flatData.length === 0) {
|
||||
console.log("no data found")
|
||||
|
||||
let curDate, curVariant, curColormap
|
||||
const dates = Object.keys(data).sort()
|
||||
curDate = dates.find(date => {
|
||||
const variantEntries = Object.entries(data[date])
|
||||
if (variantEntries.length === 0) return false
|
||||
const variantEntry = variantEntries.find(([variant, colormaps]) => variant === defaultVariant && colormaps.length > 0) ??
|
||||
variantEntries.sort(([a], [b]) => a.localeCompare(b)).find(([_, colormaps]) => colormaps.length > 0)
|
||||
if (!variantEntry) return false
|
||||
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
|
||||
}
|
||||
|
||||
let { date: curDate, variant: curVariant, colormap: curColormap } = flatData[flatData.length - 1]
|
||||
|
||||
map.addSource(tilesId, {
|
||||
type: "raster",
|
||||
tiles: [`${tilesDir}/${curDate}/${curVariant}/${curColormap}/{z}/{y}/{x}.png`],
|
||||
|
@ -334,10 +374,13 @@
|
|||
}
|
||||
})
|
||||
|
||||
let subnetsVisible = true
|
||||
map.addSource(subnetsId, {
|
||||
type: "raster",
|
||||
tiles: [`${subnetsId}://{z}/{y}/{x}`],
|
||||
tileSize,
|
||||
minzoom: 0,
|
||||
maxzoom: 12,
|
||||
})
|
||||
map.addLayer({
|
||||
id: subnetsId,
|
||||
|
@ -348,34 +391,41 @@
|
|||
const setStyle = (date, variant, colormap) => {
|
||||
if (date === curDate && variant === curVariant && colormap === curColormap || !data[date]?.[variant]?.includes(colormap))
|
||||
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
|
||||
curVariant = variant
|
||||
curColormap = colormap
|
||||
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")
|
||||
container.className = "maplibregl-ctrl maplibregl-ctrl-group custom-control"
|
||||
const header = document.createElement("h3")
|
||||
header.textContent = name
|
||||
const summary = document.createElement("summary")
|
||||
summary.replaceChildren(header)
|
||||
container.replaceChildren(summary)
|
||||
const setButtons = (buttons) => container.replaceChildren(summary, ...buttons.map(({ button }) => button))
|
||||
const addControl = () => map.addControl({
|
||||
onAdd: () => container,
|
||||
onRemove: () => container.parentNode.removeChild(container)
|
||||
}, "top-right")
|
||||
return { container, setButtons, addControl }
|
||||
const buttonContainer = document.createElement("div")
|
||||
buttonContainer.className = "button-container"
|
||||
container.replaceChildren(summary, buttonContainer)
|
||||
return {
|
||||
container,
|
||||
setButtons: buttons => buttonContainer.replaceChildren(...buttons.map(({ button }) => button)),
|
||||
addControl: (side = "top-right") => addControl(container, side)
|
||||
}
|
||||
}
|
||||
|
||||
const dateControl = customControl("Date")
|
||||
const variantControl = customControl("Variant")
|
||||
const colormapControl = customControl("Colormap")
|
||||
const dateControl = detailsControl("Date")
|
||||
const variantControl = detailsControl("Variant")
|
||||
const colormapControl = detailsControl("Colormap")
|
||||
|
||||
const dates = Object.keys(data).sort()
|
||||
const dateButtons = dates.map(date => {
|
||||
const button = document.createElement("button")
|
||||
button.textContent = date
|
||||
|
@ -421,33 +471,55 @@
|
|||
}
|
||||
|
||||
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()
|
||||
variantControl.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
|
||||
map.on("click", e => {
|
||||
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 props = 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 text = subnet < 32 ?
|
||||
`Range: ${ipToRange(ip, subnet)}${propsText}` :
|
||||
`IP: ${ipToString(ip)}${propsText}`
|
||||
curPopup = new maplibregl.Popup()
|
||||
.setHTML(text)
|
||||
.setLngLat(e.lngLat)
|
||||
.addTo(map)
|
||||
})
|
||||
const hoverTextControl = document.createElement("div")
|
||||
hoverTextControl.className = "maplibregl-ctrl maplibregl-ctrl-group"
|
||||
const hoverTextP = document.createElement("p")
|
||||
hoverTextControl.replaceChildren(hoverTextP)
|
||||
map.on("mousemove", e => hoverTextP.textContent = getIPAtMouse(e, false, false))
|
||||
addControl(hoverTextControl, "bottom-left")
|
||||
|
||||
const main = document.getElementsByTagName("main")[0]
|
||||
main.addEventListener("click", e => {
|
||||
if (e.target === main && curPopup) curPopup.remove()
|
||||
map.addControl(new maplibregl.NavigationControl({ showCompass: false }), "top-left")
|
||||
|
||||
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>
|
||||
</body>
|
||||
|
|
Loading…
Reference in New Issue