Compare commits
2
Commits
8dbb336e0a
...
cb91e5f0de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb91e5f0de | ||
|
|
043cd052b6 |
@@ -11,7 +11,7 @@ export class AdminGuard implements CanActivate {
|
|||||||
throw new UnauthorizedException('Tenant not found in request');
|
throw new UnauthorizedException('Tenant not found in request');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tenant.plan !== TenantPlan.ENTERPRISE && tenant.role !== TenantRole.ADMIN) {
|
if (tenant.plan !== TenantPlan.ENTERPRISE && tenant.role !== 'ADMIN') {
|
||||||
throw new ForbiddenException('Admin access required. Your tenant must have an ENTERPRISE plan or ADMIN clearance.');
|
throw new ForbiddenException('Admin access required. Your tenant must have an ENTERPRISE plan or ADMIN clearance.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ const MapComponent: React.FC<MapProps> = ({
|
|||||||
id: 'los-line',
|
id: 'los-line',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
source: 'los-source',
|
source: 'los-source',
|
||||||
filter: ['==', '$type', 'LineString'],
|
filter: ['==', ['geometry-type'], 'LineString'],
|
||||||
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
||||||
paint: {
|
paint: {
|
||||||
'line-color': '#f59e0b',
|
'line-color': '#f59e0b',
|
||||||
@@ -211,7 +211,7 @@ const MapComponent: React.FC<MapProps> = ({
|
|||||||
id: 'los-points',
|
id: 'los-points',
|
||||||
type: 'circle',
|
type: 'circle',
|
||||||
source: 'los-source',
|
source: 'los-source',
|
||||||
filter: ['==', '$type', 'Point'],
|
filter: ['==', ['geometry-type'], 'Point'],
|
||||||
paint: {
|
paint: {
|
||||||
'circle-radius': 8,
|
'circle-radius': 8,
|
||||||
'circle-color': [
|
'circle-color': [
|
||||||
@@ -230,7 +230,7 @@ const MapComponent: React.FC<MapProps> = ({
|
|||||||
id: 'los-labels',
|
id: 'los-labels',
|
||||||
type: 'symbol',
|
type: 'symbol',
|
||||||
source: 'los-source',
|
source: 'los-source',
|
||||||
filter: ['==', '$type', 'Point'],
|
filter: ['==', ['geometry-type'], 'Point'],
|
||||||
layout: {
|
layout: {
|
||||||
'text-field': ['get', 'label'],
|
'text-field': ['get', 'label'],
|
||||||
'text-size': 12,
|
'text-size': 12,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from 'react-dom/client'
|
||||||
import maplibregl from 'maplibre-gl'
|
import maplibregl from './utils/maplibreWorker'
|
||||||
import App from './App.tsx'
|
import App from './App.tsx'
|
||||||
import CompareView from './pages/CompareView'
|
import CompareView from './pages/CompareView'
|
||||||
import IntelligenceDashboard from './pages/IntelligenceDashboard'
|
import IntelligenceDashboard from './pages/IntelligenceDashboard'
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -167,9 +167,10 @@ export async function sampleElevationsBatch(
|
|||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutId = setTimeout(() => controller.abort(), 2500);
|
const timeoutId = setTimeout(() => controller.abort(), 2500);
|
||||||
|
|
||||||
const res = await fetch(`${apiUrl}/tactical/elevations`, {
|
const apiKey = localStorage.getItem('map_admin_key') || localStorage.getItem('intaleq_api_key') || (import.meta as any).env.VITE_ADMIN_API_KEY || (import.meta as any).env.VITE_API_KEY || 'zP9vL5mK2nQ8xR7jT4wS1yB6hG3fV0cX';
|
||||||
|
const res = await fetch(`${apiUrl}/tactical/elevations?key=${encodeURIComponent(apiKey)}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey },
|
||||||
body: JSON.stringify({ coordinates: coords }),
|
body: JSON.stringify({ coordinates: coords }),
|
||||||
signal: controller.signal
|
signal: controller.signal
|
||||||
});
|
});
|
||||||
@@ -303,13 +304,13 @@ export async function calculateLineOfSight(
|
|||||||
// Try fetching high-precision result from Backend Tactical API
|
// Try fetching high-precision result from Backend Tactical API
|
||||||
try {
|
try {
|
||||||
const apiUrl = (import.meta as any).env.VITE_API_URL || '/api';
|
const apiUrl = (import.meta as any).env.VITE_API_URL || '/api';
|
||||||
const apiKey = (import.meta as any).env.VITE_API_KEY;
|
const apiKey = (import.meta as any).env.VITE_ADMIN_API_KEY || (import.meta as any).env.VITE_API_KEY || localStorage.getItem('map_admin_key') || localStorage.getItem('intaleq_api_key') || 'zP9vL5mK2nQ8xR7jT4wS1yB6hG3fV0cX';
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutId = setTimeout(() => controller.abort(), 8000);
|
const timeoutId = setTimeout(() => controller.abort(), 8000);
|
||||||
|
|
||||||
const res = await fetch(`${apiUrl}/tactical/line-of-sight?observerLat=${startLat}&observerLng=${startLng}&targetLat=${endLat}&targetLng=${endLng}&observerHeight=${obsHeight}&targetHeight=${tgtHeight}&samples=${samples}`, {
|
const res = await fetch(`${apiUrl}/tactical/line-of-sight?observerLat=${startLat}&observerLng=${startLng}&targetLat=${endLat}&targetLng=${endLng}&observerHeight=${obsHeight}&targetHeight=${tgtHeight}&samples=${samples}&key=${encodeURIComponent(apiKey)}`, {
|
||||||
headers: apiKey ? { 'x-api-key': apiKey } : {},
|
headers: { 'x-api-key': apiKey },
|
||||||
signal: controller.signal
|
signal: controller.signal
|
||||||
});
|
});
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
@@ -490,7 +491,9 @@ export async function calculateRadialViewshed(
|
|||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutId = setTimeout(() => controller.abort(), 3500);
|
const timeoutId = setTimeout(() => controller.abort(), 3500);
|
||||||
|
|
||||||
const res = await fetch(`${apiUrl}/tactical/viewshed?lat=${centerLat}&lng=${centerLng}&height=${obsHeight}&radius=${radiusMeters}&rays=${numRays}`, {
|
const apiKey = (import.meta as any).env.VITE_ADMIN_API_KEY || (import.meta as any).env.VITE_API_KEY || localStorage.getItem('map_admin_key') || localStorage.getItem('intaleq_api_key') || 'zP9vL5mK2nQ8xR7jT4wS1yB6hG3fV0cX';
|
||||||
|
const res = await fetch(`${apiUrl}/tactical/viewshed?lat=${centerLat}&lng=${centerLng}&height=${obsHeight}&radius=${radiusMeters}&rays=${numRays}&key=${encodeURIComponent(apiKey)}`, {
|
||||||
|
headers: { 'x-api-key': apiKey },
|
||||||
signal: controller.signal
|
signal: controller.signal
|
||||||
});
|
});
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import maplibregl from 'maplibre-gl';
|
||||||
|
import workerUrl from 'maplibre-gl/dist/maplibre-gl-csp-worker.js?url';
|
||||||
|
|
||||||
|
// MapLibre 5 serializes its embedded worker from functions. Vite's class-field
|
||||||
|
// transform injects __publicField outside those functions, leaving GeoJSON
|
||||||
|
// workers with an undefined helper. Serve the matching standalone worker as an
|
||||||
|
// untransformed asset in both development and production, before any map exists.
|
||||||
|
maplibregl.setWorkerUrl(workerUrl);
|
||||||
|
|
||||||
|
export default maplibregl;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# GeoJSON overlay rendering regression
|
||||||
|
|
||||||
|
The tactical calculations return geometry, but MapLibre's GeoJSON worker fails
|
||||||
|
with `__publicField is not defined`. HTML markers and vector basemap tiles still
|
||||||
|
render, so the UI appears functional while all GeoJSON overlays remain invisible.
|
||||||
|
|
||||||
|
## Cause
|
||||||
|
|
||||||
|
Commit `0bf1382` changed Vite 8.0.1 to 6.4.3 (and the React plugin to 4.7.0).
|
||||||
|
The current optimized MapLibre 5.20.2 bundle imports `__publicField` outside the
|
||||||
|
functions that MapLibre serializes into its embedded worker. The GeoJSON indexing
|
||||||
|
code references that helper inside the worker, where it does not exist.
|
||||||
|
Changing geometry filters, opacity, layer order, or camera bounds cannot repair
|
||||||
|
this worker failure.
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
|
||||||
|
`src/utils/maplibreWorker.ts` loads the installed MapLibre 5 standalone CSP worker
|
||||||
|
using Vite's asset URL import and calls `setWorkerUrl` before React creates maps.
|
||||||
|
`src/main.tsx` imports this configured MapLibre instance. Other components share
|
||||||
|
the same underlying MapLibre module. No API or geometry calculation change is
|
||||||
|
required.
|
||||||
|
|
||||||
|
The `?url` import is intentional for this self-contained MapLibre 5 worker: it
|
||||||
|
preserves the vendor file without transpilation. Revisit the worker entry point
|
||||||
|
if upgrading MapLibre to a new major version.
|
||||||
|
|
||||||
|
## Verification (2026-09-24)
|
||||||
|
|
||||||
|
- Reproduced the missing overlays on the official tactical site, and confirmed
|
||||||
|
its Isochrone API returned three valid Polygon features.
|
||||||
|
- A minimal map with an empty base style and a fixed line/polygon reproduced the
|
||||||
|
same failure: zero source/rendered features and `__publicField is not defined`.
|
||||||
|
- With the standalone worker, that same map visibly drew both geometries, with
|
||||||
|
eight tile-level source/rendered feature entries and no worker error.
|
||||||
|
- Tested the modified tactical UI locally, proxying API requests to the official
|
||||||
|
service: terrain overlays, the 10.9 km² / 14% viewshed, and colored Isochrone
|
||||||
|
regions rendered. Temporary diagnostic geometry was removed afterward.
|
||||||
|
- `npm run build` succeeds. The emitted standalone worker is byte-identical to
|
||||||
|
the installed vendor worker.
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
Deploy `apps/web/src/main.tsx` and `apps/web/src/utils/maplibreWorker.ts` together.
|
||||||
|
The observed official site serves Vite development modules; restart its web
|
||||||
|
service and reload the browser after deploying. For a production build, deploy
|
||||||
|
the complete `dist` output, including the emitted worker asset. The live site
|
||||||
|
has not been changed by this local fix.
|
||||||
Reference in New Issue
Block a user