refactor(map): fix overlay anchors and migrate mini-maps to OpenFreeMap with attribution

This commit is contained in:
gadgethd
2026-08-27 23:48:01 +00:00
parent fda05b835f
commit 0a41d4bfc6
6 changed files with 27 additions and 43 deletions
@@ -13,6 +13,14 @@ import {
const SOURCE_ID = 'hopreach-rf-source';
const LAYER_ID = 'hopreach-rf-layer';
function getFirstSymbolLayerId(map: maplibregl.Map): string | undefined {
const layers = map.getStyle().layers;
for (const layer of layers) {
if (layer.type === 'symbol') return layer.id;
}
return undefined;
}
function removeRfLayers(map: maplibregl.Map): void {
if (map.getLayer(LAYER_ID)) map.removeLayer(LAYER_ID);
if (map.getSource(SOURCE_ID)) map.removeSource(SOURCE_ID);
@@ -61,11 +69,7 @@ export function RfCoverageOverlay({
releaseDataset = null;
if (!visible || tiles.length === 0) return;
const beforeId = map.getLayer('map-labels-water')
? 'map-labels-water'
: map.getLayer('privacy-rings-layer')
? 'privacy-rings-layer'
: undefined;
const beforeId = getFirstSymbolLayerId(map);
const dataset = registerRfRasterDataset(
tiles.map((tile) => ({
+9 -1
View File
@@ -34,9 +34,17 @@ type TopologyMapOverlayProps = Omit<TopologyMapProps, 'network' | 'observer' | '
onNodeSelect: (nodeId: string) => void;
};
function getFirstSymbolLayerId(map: maplibregl.Map): string | undefined {
const layers = map.getStyle().layers;
for (const layer of layers) {
if (layer.type === 'symbol') return layer.id;
}
return undefined;
}
function addTopologyLayer(map: maplibregl.Map, layer: maplibregl.LayerSpecification): void {
if (map.getLayer(layer.id)) return;
const beforeId = map.getLayer('map-labels-water') ? 'map-labels-water' : undefined;
const beforeId = getFirstSymbolLayerId(map);
if (beforeId) map.addLayer(layer, beforeId);
else map.addLayer(layer);
}
@@ -1,6 +1,7 @@
import React, { lazy, memo, Suspense, useEffect } from 'react';
import type * as maplibregl from 'maplibre-gl';
import { AnimatedPathOverlay, type AerialPath } from '../Map/AnimatedPathOverlay.js';
import { OPENFREEMAP_STYLE_DARK } from '../Map/mapConfig.js';
export const C_CYAN = '#00c4ff';
export const C_GREEN = '#00e676';
@@ -253,13 +254,6 @@ export const EmptyPacketState: React.FC<{ label?: string }> = ({
label = 'No packet data in this window.',
}) => <div className="stats-page__empty">{label}</div>;
const CARTO_DARK_TILES = [
'https://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',
'https://b.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',
'https://c.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',
'https://d.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',
];
export const DecodedPathMapView: React.FC<{ nodes: DecodedPathNode[] }> = ({ nodes }) => {
const containerRef = React.useRef<HTMLDivElement>(null);
const [mapInstance, setMapInstance] = React.useState<maplibregl.Map | null>(null);
@@ -284,23 +278,11 @@ export const DecodedPathMapView: React.FC<{ nodes: DecodedPathNode[] }> = ({ nod
const maplibre = maplibreModule;
map = new maplibre.Map({
container: containerRef.current,
style: {
version: 8,
sources: {
tiles: {
type: 'raster',
tiles: CARTO_DARK_TILES,
tileSize: 256,
maxzoom: 19,
attribution: '© OpenStreetMap © CARTO',
},
},
layers: [{ id: 'bg', type: 'raster', source: 'tiles' }],
},
style: OPENFREEMAP_STYLE_DARK,
center: [Number(nodes[0]!.lon), Number(nodes[0]!.lat)],
zoom: 8,
attributionControl: false,
});
map.addControl(new maplibre.AttributionControl({ compact: true }));
map.on('load', () => {
if (cancelled || !map) return;
const bounds = new maplibre.LngLatBounds();
+2 -2
View File
@@ -177,8 +177,8 @@ const OriginMiniMap = memo(function OriginMiniMap({
style: MAP_STYLE,
bounds,
fitBoundsOptions: { padding: 40, maxZoom: 10 },
attributionControl: false,
});
map.addControl(new maplibregl.AttributionControl({ compact: true }));
map.addControl(new maplibregl.NavigationControl({ showCompass: false }), 'top-right');
map.on('load', () => {
map.addSource('zone', {
@@ -488,8 +488,8 @@ export function SpamPage() {
style: MAP_STYLE,
center: [-2.5, 54.0],
zoom: 4.6,
attributionControl: false,
});
map.addControl(new maplibregl.AttributionControl({ compact: true }));
map.addControl(new maplibregl.NavigationControl({ showCompass: false }), 'top-right');
map.on('load', () => {
map.addSource('zones', { type: 'geojson', data: EMPTY_FC });
+1 -1
View File
@@ -111,8 +111,8 @@ export const OwnerMapView: React.FC<{
style: MAP_STYLE,
center: [DEFAULT_CENTER[1], DEFAULT_CENTER[0]],
zoom: 7,
attributionControl: false,
});
map.addControl(new maplibregl.AttributionControl({ compact: true }));
mapRef.current = map;
// Keep the map as a static regional backdrop while preserving marker clicks.
+3 -13
View File
@@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import type * as maplibregl from 'maplibre-gl';
import { AnimatedPathOverlay, type AerialPath, type AerialPathNode } from '../../components/Map/AnimatedPathOverlay.js';
import { LoadingIndicator } from '../../components/LoadingIndicator.js';
import { OPENFREEMAP_STYLE_DARK } from '../../components/Map/mapConfig.js';
import type { MeshNode } from '../../hooks/useNodes.js';
import {
multiObserverPathRoutes,
@@ -42,13 +43,6 @@ export type LazyPathResult = {
paths: LazyPath[];
};
const CARTO_TILES = [
'https://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',
'https://b.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',
'https://c.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',
'https://d.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',
];
function lazyPathRuns(nodes: readonly LazyPathNode[]): LazyPathNode[][] {
const runs: LazyPathNode[][] = [];
let current: LazyPathNode[] = [];
@@ -193,17 +187,13 @@ export const PathMap: React.FC<{
maplibreRef.current = maplibre;
nextMap = new maplibre.Map({
container: containerRef.current,
style: {
version: 8,
sources: { tiles: { type: 'raster', tiles: CARTO_TILES, tileSize: 256, maxzoom: 19, attribution: '© OpenStreetMap © CARTO' } },
layers: [{ id: 'bg', type: 'raster', source: 'tiles' }],
},
style: OPENFREEMAP_STYLE_DARK,
center: [0, 51.5],
zoom: 6,
pitch: 50,
bearing: -8,
attributionControl: false,
});
nextMap.addControl(new maplibre.AttributionControl({ compact: true }));
mapRef.current = nextMap;
nextMap.on('load', () => {
if (cancelled || !nextMap) return;