Use proper Maidenhead grid overlay on rover page
Extract GridSquare class and updateGridOverlay from propagation_map_hook into shared maidenhead_grid.js module. Rover map now shows the same zoom-adaptive Maidenhead grid overlay as the main /map page — red rectangles with labels that switch between field (2-char) and square (4-char) based on zoom level. Updates on pan/zoom. Toggle button hides/shows the grid overlay.
This commit is contained in:
parent
3c0b518af1
commit
efe7103fe1
2 changed files with 135 additions and 15 deletions
122
assets/js/maidenhead_grid.js
Normal file
122
assets/js/maidenhead_grid.js
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
// Shared Maidenhead grid overlay for Leaflet maps
|
||||
// Extracted from propagation_map_hook.js for reuse across map pages
|
||||
|
||||
export class GridSquare {
|
||||
constructor(lat, lon) {
|
||||
this.lat = lat
|
||||
this.lon = ((lon % 360) + 540) % 360 - 180
|
||||
}
|
||||
|
||||
encode(precision = 4) {
|
||||
const adjLon = this.lon + 180
|
||||
const adjLat = this.lat + 90
|
||||
let g = ""
|
||||
g += String.fromCharCode(65 + Math.floor(adjLon / 20))
|
||||
g += String.fromCharCode(65 + Math.floor(adjLat / 10))
|
||||
g += Math.floor((adjLon % 20) / 2)
|
||||
g += Math.floor(adjLat % 10)
|
||||
if (precision > 4) {
|
||||
g += String.fromCharCode(97 + Math.floor(((adjLon % 2) * 60) / 5))
|
||||
g += String.fromCharCode(97 + Math.floor(((adjLat % 1) * 60) / 2.5))
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
static decode(grid) {
|
||||
const f1 = grid.charCodeAt(0) - 65
|
||||
const f2 = grid.charCodeAt(1) - 65
|
||||
let west = f1 * 20 - 180
|
||||
let south = f2 * 10 - 90
|
||||
let w = 20, h = 10
|
||||
|
||||
if (grid.length >= 4) {
|
||||
west += parseInt(grid[2]) * 2
|
||||
south += parseInt(grid[3])
|
||||
w = 2; h = 1
|
||||
}
|
||||
if (grid.length >= 6) {
|
||||
west += (grid.charCodeAt(4) - 97) * 5 / 60
|
||||
south += (grid.charCodeAt(5) - 97) * 2.5 / 60
|
||||
w = 5 / 60; h = 2.5 / 60
|
||||
}
|
||||
return {
|
||||
west, east: west + w, south, north: south + h,
|
||||
center: { lat: south + h / 2, lon: west + w / 2 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawGridSquare(grid, precision, bounds, zoom, layerGroup, drawnSet, map) {
|
||||
if (grid.length < 2 || drawnSet.has(grid)) return
|
||||
drawnSet.add(grid)
|
||||
|
||||
const c = GridSquare.decode(grid)
|
||||
if (c.west > bounds.getEast() || c.east < bounds.getWest() ||
|
||||
c.south > bounds.getNorth() || c.north < bounds.getSouth()) return
|
||||
|
||||
const field = precision <= 2
|
||||
|
||||
layerGroup.addLayer(L.rectangle(
|
||||
[[c.south, c.west], [c.north, c.east]],
|
||||
{ color: "#E63946", weight: field ? 2.5 : 2, opacity: 0.8, fillOpacity: 0, interactive: false }
|
||||
))
|
||||
|
||||
const sw = map.latLngToContainerPoint([c.south, c.west])
|
||||
const ne = map.latLngToContainerPoint([c.north, c.east])
|
||||
const px = Math.abs(ne.x - sw.x)
|
||||
const minW = field ? 30 : 40
|
||||
if (px > minW) {
|
||||
const len = field ? 2 : 4
|
||||
const text = grid.substring(0, len)
|
||||
const fs = Math.max(10, Math.min(14, Math.round(px / (len * 1.2))))
|
||||
layerGroup.addLayer(L.marker([c.center.lat, c.center.lon], {
|
||||
interactive: false,
|
||||
icon: L.divIcon({
|
||||
className: "grid-label",
|
||||
html: `<div style="font-size:${fs}px;font-weight:bold;color:#fff;background:rgba(0,0,0,0.5);padding:1px 4px;border-radius:3px;white-space:nowrap;width:fit-content;">${text}</div>`,
|
||||
iconSize: [Math.round(len * fs * 0.8), Math.round(fs * 1.5)],
|
||||
iconAnchor: [Math.round(len * fs * 0.4), Math.round(fs * 0.75)]
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
export function updateGridOverlay(map, gridLayer) {
|
||||
gridLayer.clearLayers()
|
||||
const drawn = new Set()
|
||||
const bounds = map.getBounds()
|
||||
const zoom = map.getZoom()
|
||||
const showSquares = zoom >= 7
|
||||
|
||||
const south = Math.max(bounds.getSouth(), -90)
|
||||
const north = Math.min(bounds.getNorth(), 90)
|
||||
|
||||
if (!showSquares) {
|
||||
const fW = Math.floor((bounds.getWest() + 180) / 20) * 20 - 180
|
||||
const fE = Math.ceil((bounds.getEast() + 180) / 20) * 20 - 180
|
||||
const fS = Math.floor((south + 90) / 10) * 10 - 90
|
||||
const fN = Math.ceil((north + 90) / 10) * 10 - 90
|
||||
for (let lon = fW; lon < fE; lon += 20) {
|
||||
for (let lat = fS; lat < fN; lat += 10) {
|
||||
const nLon = ((lon % 360) + 540) % 360 - 180
|
||||
const g = new GridSquare(lat + 5, nLon + 10).encode(4).substring(0, 2)
|
||||
drawGridSquare(g, 2, bounds, zoom, gridLayer, drawn, map)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const wE = Math.floor((bounds.getWest() + 180) / 2) * 2 - 180
|
||||
const eE = Math.ceil((bounds.getEast() + 180) / 2) * 2 - 180
|
||||
const sE = Math.floor(south + 90) - 90
|
||||
const nE = Math.ceil(north + 90) - 90
|
||||
if (Math.ceil((eE - wE) / 2) * Math.ceil(nE - sE) > 500) return
|
||||
|
||||
for (let lon = wE; lon < eE; lon += 2) {
|
||||
for (let lat = sE; lat < nE; lat += 1) {
|
||||
const nLon = ((lon % 360) + 540) % 360 - 180
|
||||
const g = new GridSquare(lat + 0.5, nLon + 1).encode(4)
|
||||
drawGridSquare(g.substring(0, 4), 4, bounds, zoom, gridLayer, drawn, map)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,12 @@
|
|||
import { updateGridOverlay } from "./maidenhead_grid"
|
||||
|
||||
export const RoverMap = {
|
||||
mounted() {
|
||||
this.stations = []
|
||||
this.stationMarkers = L.layerGroup()
|
||||
this.coverageLayer = L.layerGroup()
|
||||
this.gridLayer = L.layerGroup()
|
||||
this.gridVisible = true
|
||||
|
||||
const map = L.map(this.el, {
|
||||
center: [33.2, -97.1],
|
||||
|
|
@ -18,9 +22,13 @@ export const RoverMap = {
|
|||
this.map = map
|
||||
this.stationMarkers.addTo(map)
|
||||
this.coverageLayer.addTo(map)
|
||||
this.gridLayer.addTo(map)
|
||||
|
||||
this.gridLayer = L.layerGroup().addTo(map)
|
||||
this.drawGridOverlay()
|
||||
// Draw grid and update on pan/zoom
|
||||
updateGridOverlay(map, this.gridLayer)
|
||||
map.on("moveend", () => {
|
||||
if (this.gridVisible) updateGridOverlay(map, this.gridLayer)
|
||||
})
|
||||
|
||||
this.handleEvent("stations_updated", ({ stations }) => {
|
||||
this.stations = stations
|
||||
|
|
@ -32,9 +40,12 @@ export const RoverMap = {
|
|||
})
|
||||
|
||||
this.handleEvent("toggle_grids", ({ show }) => {
|
||||
this.gridVisible = show
|
||||
if (show) {
|
||||
this.gridLayer.addTo(this.map)
|
||||
updateGridOverlay(this.map, this.gridLayer)
|
||||
} else {
|
||||
this.gridLayer.clearLayers()
|
||||
this.map.removeLayer(this.gridLayer)
|
||||
}
|
||||
})
|
||||
|
|
@ -159,19 +170,6 @@ export const RoverMap = {
|
|||
return "#ff4f4f"
|
||||
},
|
||||
|
||||
drawGridOverlay() {
|
||||
for (let lat = 25; lat <= 50; lat += 1) {
|
||||
L.polyline([[lat, -125], [lat, -66]], {
|
||||
color: "#888", weight: 0.5, opacity: 0.3
|
||||
}).addTo(this.gridLayer)
|
||||
}
|
||||
for (let lon = -125; lon <= -66; lon += 2) {
|
||||
L.polyline([[25, lon], [50, lon]], {
|
||||
color: "#888", weight: 0.5, opacity: 0.3
|
||||
}).addTo(this.gridLayer)
|
||||
}
|
||||
},
|
||||
|
||||
fitBounds() {
|
||||
const points = this.stations.map(s => [s.lat, s.lon])
|
||||
if (points.length >= 2) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue