diff --git a/src/app/api/eterra/tiles/orto/route.ts b/src/app/api/eterra/tiles/orto/route.ts index 698ea17..ead1f41 100644 --- a/src/app/api/eterra/tiles/orto/route.ts +++ b/src/app/api/eterra/tiles/orto/route.ts @@ -31,12 +31,19 @@ function tileToBbox(z: number, x: number, y: number): [number, number, number, n return [lonW, latS, lonE, latN]; } -/** Reproject WGS84 bbox to EPSG:3844 */ +/** Reproject WGS84 bbox to EPSG:3844 using all 4 corners (handles projection curvature) */ function bboxTo3844(bbox4326: [number, number, number, number]): [number, number, number, number] { const [w, s, e, n] = bbox4326; - const sw = proj4("EPSG:4326", "EPSG:3844", [w, s]); - const ne = proj4("EPSG:4326", "EPSG:3844", [e, n]); - return [sw[0]!, sw[1]!, ne[0]!, ne[1]!]; + // Project all 4 corners to handle non-linear projection + const corners = [ + proj4("EPSG:4326", "EPSG:3844", [w, s]), + proj4("EPSG:4326", "EPSG:3844", [e, s]), + proj4("EPSG:4326", "EPSG:3844", [w, n]), + proj4("EPSG:4326", "EPSG:3844", [e, n]), + ]; + const xs = corners.map((c) => c[0]!); + const ys = corners.map((c) => c[1]!); + return [Math.min(...xs), Math.min(...ys), Math.max(...xs), Math.max(...ys)]; } // Simple in-memory cookie cache for eTerra session diff --git a/src/modules/geoportal/components/basemap-switcher.tsx b/src/modules/geoportal/components/basemap-switcher.tsx index b0d98b6..9335177 100644 --- a/src/modules/geoportal/components/basemap-switcher.tsx +++ b/src/modules/geoportal/components/basemap-switcher.tsx @@ -8,7 +8,8 @@ import type { BasemapId } from "../types"; const BASEMAPS: { id: BasemapId; label: string; icon: typeof Map }[] = [ { id: "liberty", label: "Harta", icon: Map }, { id: "dark", label: "Dark", icon: Moon }, - { id: "satellite", label: "Satelit", icon: Satellite }, + { id: "google", label: "Satelit", icon: Satellite }, + { id: "satellite", label: "ESRI", icon: Satellite }, { id: "orto", label: "ANCPI", icon: TreePine }, ]; diff --git a/src/modules/geoportal/components/feature-info-panel.tsx b/src/modules/geoportal/components/feature-info-panel.tsx index 01149a9..2c009f5 100644 --- a/src/modules/geoportal/components/feature-info-panel.tsx +++ b/src/modules/geoportal/components/feature-info-panel.tsx @@ -133,7 +133,11 @@ export function FeatureInfoPanel({ feature, onClose }: FeatureInfoPanelProps) { <>
- + {e?.PROPRIETARI_VECHI && e.PROPRIETARI_VECHI !== "-" && ( +
+ +
+ )} diff --git a/src/modules/geoportal/components/map-viewer.tsx b/src/modules/geoportal/components/map-viewer.tsx index 172b3f2..c0663b2 100644 --- a/src/modules/geoportal/components/map-viewer.tsx +++ b/src/modules/geoportal/components/map-viewer.tsx @@ -80,6 +80,13 @@ const BASEMAPS: Record = { attribution: '© Esri, Maxar', tileSize: 256, }, + google: { + type: "raster", + tiles: ["https://mt0.google.com/vt/lyrs=s&x={x}&y={y}&z={z}"], + attribution: '© Google', + tileSize: 256, + maxzoom: 20, + }, orto: { type: "raster", tiles: ["/api/eterra/tiles/orto?z={z}&x={x}&y={y}"], @@ -383,15 +390,11 @@ export const MapViewer = forwardRef( // === UAT z5-8: coarse === map.addSource(SOURCES.uatsZ5, { type: "vector", tiles: [`${m}/${SOURCES.uatsZ5}/{z}/{x}/{y}`], minzoom: 5, maxzoom: 8 }); - map.addLayer({ id: LAYER_IDS.uatsZ5Fill, type: "fill", source: SOURCES.uatsZ5, "source-layer": SOURCES.uatsZ5, minzoom: 5, maxzoom: 8, - paint: { "fill-color": "#8b5cf6", "fill-opacity": 0.03 } }); map.addLayer({ id: LAYER_IDS.uatsZ5Line, type: "line", source: SOURCES.uatsZ5, "source-layer": SOURCES.uatsZ5, minzoom: 5, maxzoom: 8, paint: { "line-color": "#7c3aed", "line-width": 0.6 } }); // === UAT z8-12: moderate === map.addSource(SOURCES.uatsZ8, { type: "vector", tiles: [`${m}/${SOURCES.uatsZ8}/{z}/{x}/{y}`], minzoom: 8, maxzoom: 12 }); - map.addLayer({ id: LAYER_IDS.uatsZ8Fill, type: "fill", source: SOURCES.uatsZ8, "source-layer": SOURCES.uatsZ8, minzoom: 8, maxzoom: 12, - paint: { "fill-color": "#8b5cf6", "fill-opacity": 0.05 } }); map.addLayer({ id: LAYER_IDS.uatsZ8Line, type: "line", source: SOURCES.uatsZ8, "source-layer": SOURCES.uatsZ8, minzoom: 8, maxzoom: 12, paint: { "line-color": "#7c3aed", "line-width": 1 } }); map.addLayer({ id: LAYER_IDS.uatsZ8Label, type: "symbol", source: SOURCES.uatsZ8, "source-layer": SOURCES.uatsZ8, minzoom: 9, maxzoom: 12, @@ -400,8 +403,6 @@ export const MapViewer = forwardRef( // === UAT z12+: full detail (no simplification) === map.addSource(SOURCES.uatsZ12, { type: "vector", tiles: [`${m}/${SOURCES.uatsZ12}/{z}/{x}/{y}`], minzoom: 12, maxzoom: 16 }); - map.addLayer({ id: LAYER_IDS.uatsZ12Fill, type: "fill", source: SOURCES.uatsZ12, "source-layer": SOURCES.uatsZ12, minzoom: 12, - paint: { "fill-color": "#8b5cf6", "fill-opacity": 0.08 } }); map.addLayer({ id: LAYER_IDS.uatsZ12Line, type: "line", source: SOURCES.uatsZ12, "source-layer": SOURCES.uatsZ12, minzoom: 12, paint: { "line-color": "#7c3aed", "line-width": 2 } }); map.addLayer({ id: LAYER_IDS.uatsZ12Label, type: "symbol", source: SOURCES.uatsZ12, "source-layer": SOURCES.uatsZ12, minzoom: 12, @@ -473,7 +474,6 @@ export const MapViewer = forwardRef( /* ---- Click handler — NO popup, only callback ---- */ const clickableLayers = [ LAYER_IDS.terenuriFill, LAYER_IDS.cladiriFill, - LAYER_IDS.uatsZ5Fill, LAYER_IDS.uatsZ8Fill, LAYER_IDS.uatsZ12Fill, ]; map.on("click", (e) => { @@ -685,6 +685,25 @@ export const MapViewer = forwardRef( canvas.addEventListener("mousemove", onRectMouseMove); canvas.addEventListener("mouseup", onRectMouseUp); + /* ---- ESC key exits selection mode ---- */ + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape" && selectionTypeRef.current !== "off") { + onFeatureClick?.(null as unknown as ClickedFeature); + clearDrawState(); + // Notify parent to turn off selection + onSelectionChange?.([]); + } + }; + document.addEventListener("keydown", onKeyDown); + + /* ---- Right-click exits selection / cancels draw ---- */ + map.on("contextmenu", (e) => { + if (selectionTypeRef.current !== "off") { + e.preventDefault(); + clearDrawState(); + } + }); + /* ---- Cursor change ---- */ for (const lid of clickableLayers) { map.on("mouseenter", lid, () => { map.getCanvas().style.cursor = "pointer"; }); @@ -693,6 +712,7 @@ export const MapViewer = forwardRef( /* ---- Cleanup ---- */ return () => { + document.removeEventListener("keydown", onKeyDown); canvas.removeEventListener("mousedown", onRectMouseDown); canvas.removeEventListener("mousemove", onRectMouseMove); canvas.removeEventListener("mouseup", onRectMouseUp); diff --git a/src/modules/geoportal/components/selection-toolbar.tsx b/src/modules/geoportal/components/selection-toolbar.tsx index 58bee30..de7b1ea 100644 --- a/src/modules/geoportal/components/selection-toolbar.tsx +++ b/src/modules/geoportal/components/selection-toolbar.tsx @@ -29,9 +29,9 @@ const EXPORT_FORMATS: { id: ExportFormat; label: string }[] = [ ]; const MODE_BUTTONS: { mode: SelectionMode; icon: typeof MousePointerClick; label: string; tooltip: string }[] = [ - { mode: "click", icon: MousePointerClick, label: "Click", tooltip: "Click pe parcele individuale pentru a le selecta/deselecta" }, - { mode: "rect", icon: Square, label: "Dreptunghi", tooltip: "Trage un dreptunghi pe harta pentru a selecta toate parcelele din zona" }, - { mode: "freehand", icon: PenTool, label: "Desen", tooltip: "Deseneaza o zona libera pe harta (click puncte, dublu-click pentru a inchide)" }, + { mode: "click", icon: MousePointerClick, label: "Click", tooltip: "Click pe parcele individuale. ESC sau right-click pentru a iesi." }, + { mode: "rect", icon: Square, label: "Dreptunghi", tooltip: "Trage un dreptunghi pe harta. ESC sau right-click pentru a anula." }, + { mode: "freehand", icon: PenTool, label: "Desen", tooltip: "Click puncte pe harta, dublu-click inchide. ESC sau right-click anuleaza." }, ]; export function SelectionToolbar({ diff --git a/src/modules/geoportal/types.ts b/src/modules/geoportal/types.ts index bb3ad44..1789335 100644 --- a/src/modules/geoportal/types.ts +++ b/src/modules/geoportal/types.ts @@ -46,7 +46,7 @@ export type MapViewState = { /* Basemap */ /* ------------------------------------------------------------------ */ -export type BasemapId = "liberty" | "dark" | "satellite" | "orto"; +export type BasemapId = "liberty" | "dark" | "satellite" | "google" | "orto"; export type BasemapDef = { id: BasemapId;