show cable lenght info
This commit is contained in:
@@ -1,12 +1,39 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { Polyline, Marker, Popup, Circle, useMapEvents } from 'react-leaflet';
|
import { Polyline, Marker, Popup, Circle, Tooltip, useMapEvents } from 'react-leaflet';
|
||||||
import L from 'leaflet';
|
import L from 'leaflet';
|
||||||
import { mapItemService } from '../../services/mapItemService';
|
import { mapItemService } from '../../services/mapItemService';
|
||||||
import { CABLE_COLORS, type MapItem, type CableType } from '../../types/mapItem';
|
import { CABLE_COLORS, type MapItem, type CableType } from '../../types/mapItem';
|
||||||
import { ItemContextMenu } from './ItemContextMenu';
|
import { ItemContextMenu } from './ItemContextMenu';
|
||||||
import { useDrawingStore } from '../../stores/drawingStore';
|
import { useDrawingStore } from '../../stores/drawingStore';
|
||||||
|
|
||||||
|
// Calculate distance between two lat/lng points in meters using Haversine formula
|
||||||
|
function calculateDistance(lat1: number, lng1: number, lat2: number, lng2: number): number {
|
||||||
|
const R = 6371e3; // Earth's radius in meters
|
||||||
|
const φ1 = (lat1 * Math.PI) / 180;
|
||||||
|
const φ2 = (lat2 * Math.PI) / 180;
|
||||||
|
const Δφ = ((lat2 - lat1) * Math.PI) / 180;
|
||||||
|
const Δλ = ((lng2 - lng1) * Math.PI) / 180;
|
||||||
|
|
||||||
|
const a =
|
||||||
|
Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
|
||||||
|
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
|
||||||
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||||
|
|
||||||
|
return R * c; // Distance in meters
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format distance for display
|
||||||
|
function formatDistance(meters: number): string {
|
||||||
|
if (meters < 1) {
|
||||||
|
return `${Math.round(meters * 100)} cm`;
|
||||||
|
} else if (meters < 1000) {
|
||||||
|
return `${Math.round(meters * 10) / 10} m`;
|
||||||
|
} else {
|
||||||
|
return `${Math.round(meters / 100) / 10} km`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface MapItemsLayerProps {
|
interface MapItemsLayerProps {
|
||||||
mapId: string;
|
mapId: string;
|
||||||
refreshTrigger: number;
|
refreshTrigger: number;
|
||||||
@@ -128,6 +155,20 @@ export function MapItemsLayer({ mapId, refreshTrigger, readOnly = false }: MapIt
|
|||||||
const cableType = item.properties.cable_type as CableType;
|
const cableType = item.properties.cable_type as CableType;
|
||||||
const color = CABLE_COLORS[cableType] || '#6B7280';
|
const color = CABLE_COLORS[cableType] || '#6B7280';
|
||||||
|
|
||||||
|
// Calculate distances
|
||||||
|
const segmentDistances: number[] = [];
|
||||||
|
let totalDistance = 0;
|
||||||
|
for (let i = 0; i < positions.length - 1; i++) {
|
||||||
|
const dist = calculateDistance(
|
||||||
|
positions[i][0],
|
||||||
|
positions[i][1],
|
||||||
|
positions[i + 1][0],
|
||||||
|
positions[i + 1][1]
|
||||||
|
);
|
||||||
|
segmentDistances.push(dist);
|
||||||
|
totalDistance += dist;
|
||||||
|
}
|
||||||
|
|
||||||
// Find connected devices
|
// Find connected devices
|
||||||
const startDevice = item.properties.start_device_id
|
const startDevice = item.properties.start_device_id
|
||||||
? items.find(i => i.id === item.properties.start_device_id)
|
? items.find(i => i.id === item.properties.start_device_id)
|
||||||
@@ -160,6 +201,19 @@ export function MapItemsLayer({ mapId, refreshTrigger, readOnly = false }: MapIt
|
|||||||
<div className="text-sm dark:bg-gray-800 dark:text-white" style={{ minWidth: '200px' }}>
|
<div className="text-sm dark:bg-gray-800 dark:text-white" style={{ minWidth: '200px' }}>
|
||||||
<div className="font-semibold text-gray-900 dark:text-white">{item.properties.name || 'Cable'}</div>
|
<div className="font-semibold text-gray-900 dark:text-white">{item.properties.name || 'Cable'}</div>
|
||||||
<div className="text-gray-600 dark:text-gray-400">Type: {cableType}</div>
|
<div className="text-gray-600 dark:text-gray-400">Type: {cableType}</div>
|
||||||
|
<div className="text-gray-600 dark:text-gray-400 mt-1">
|
||||||
|
<span className="font-semibold">Total Length:</span> {formatDistance(totalDistance)}
|
||||||
|
</div>
|
||||||
|
{segmentDistances.length > 1 && (
|
||||||
|
<div className="mt-2 pt-2 border-t border-gray-200 dark:border-gray-700">
|
||||||
|
<div className="font-semibold text-gray-700 dark:text-gray-300 mb-1">Segment Distances:</div>
|
||||||
|
{segmentDistances.map((dist, idx) => (
|
||||||
|
<div key={idx} className="text-xs text-gray-600 dark:text-gray-400 ml-2">
|
||||||
|
Segment {idx + 1}: {formatDistance(dist)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{startDevice && (
|
{startDevice && (
|
||||||
<div className="text-gray-600 dark:text-gray-400 mt-1">
|
<div className="text-gray-600 dark:text-gray-400 mt-1">
|
||||||
From: {startDevice.properties.name || startDevice.type}
|
From: {startDevice.properties.name || startDevice.type}
|
||||||
@@ -214,6 +268,14 @@ export function MapItemsLayer({ mapId, refreshTrigger, readOnly = false }: MapIt
|
|||||||
([lng, lat]) => [lat, lng] as [number, number]
|
([lng, lat]) => [lat, lng] as [number, number]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Calculate distance
|
||||||
|
const distance = calculateDistance(
|
||||||
|
positions[0][0],
|
||||||
|
positions[0][1],
|
||||||
|
positions[1][0],
|
||||||
|
positions[1][1]
|
||||||
|
);
|
||||||
|
|
||||||
// Find connected APs
|
// Find connected APs
|
||||||
const startAp = item.properties.start_ap_id
|
const startAp = item.properties.start_ap_id
|
||||||
? items.find(i => i.id === item.properties.start_ap_id)
|
? items.find(i => i.id === item.properties.start_ap_id)
|
||||||
@@ -246,6 +308,9 @@ export function MapItemsLayer({ mapId, refreshTrigger, readOnly = false }: MapIt
|
|||||||
<Popup>
|
<Popup>
|
||||||
<div className="text-sm dark:bg-gray-800 dark:text-white" style={{ minWidth: '200px' }}>
|
<div className="text-sm dark:bg-gray-800 dark:text-white" style={{ minWidth: '200px' }}>
|
||||||
<div className="font-semibold text-gray-900 dark:text-white">{item.properties.name || 'Wireless Mesh'}</div>
|
<div className="font-semibold text-gray-900 dark:text-white">{item.properties.name || 'Wireless Mesh'}</div>
|
||||||
|
<div className="text-gray-600 dark:text-gray-400 mt-1">
|
||||||
|
<span className="font-semibold">Distance:</span> {formatDistance(distance)}
|
||||||
|
</div>
|
||||||
{startAp && (
|
{startAp && (
|
||||||
<div className="text-gray-600 dark:text-gray-400 mt-1">
|
<div className="text-gray-600 dark:text-gray-400 mt-1">
|
||||||
From: {startAp.properties.name || startAp.type}
|
From: {startAp.properties.name || startAp.type}
|
||||||
|
|||||||
@@ -124,3 +124,23 @@
|
|||||||
border-color: #F59E0B;
|
border-color: #F59E0B;
|
||||||
color: #F59E0B;
|
color: #F59E0B;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Distance labels on map */
|
||||||
|
.distance-label {
|
||||||
|
background-color: rgba(255, 255, 255, 0.95) !important;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.2) !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
padding: 2px 6px !important;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .distance-label {
|
||||||
|
background-color: rgba(31, 41, 55, 0.95) !important;
|
||||||
|
border-color: rgba(255, 255, 255, 0.2) !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.distance-label::before {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user