feat(geoportal): OpenFreeMap vector basemaps + eTerra ORTO 2024 ortophoto
Basemap options: - Liberty (OpenFreeMap vector) — default, sharp vector tiles - Dark (OpenFreeMap) — dark theme, auto-styled - Satellite (ESRI World Imagery) — raster - ANCPI Ortofoto 2024 — proxied via /api/eterra/tiles/orto, converts Web Mercator z/x/y to EPSG:3844 bbox, authenticates with eTerra session, caches 24h. Requires ETERRA_USERNAME/PASSWORD env vars. Replaces old raster OSM/OpenTopoMap with vector styles. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import { Map, Mountain, Satellite } from "lucide-react";
|
||||
import { Map, Moon, Satellite, TreePine } from "lucide-react";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
import { cn } from "@/shared/lib/utils";
|
||||
import type { BasemapId } from "../types";
|
||||
|
||||
const BASEMAPS: { id: BasemapId; label: string; icon: typeof Map }[] = [
|
||||
{ id: "osm", label: "Harta", icon: Map },
|
||||
{ id: "liberty", label: "Harta", icon: Map },
|
||||
{ id: "dark", label: "Dark", icon: Moon },
|
||||
{ id: "satellite", label: "Satelit", icon: Satellite },
|
||||
{ id: "topo", label: "Teren", icon: Mountain },
|
||||
{ id: "orto", label: "ANCPI", icon: TreePine },
|
||||
];
|
||||
|
||||
type BasemapSwitcherProps = {
|
||||
|
||||
@@ -40,7 +40,7 @@ export function GeoportalModule() {
|
||||
const mapHandleRef = useRef<MapViewerHandle>(null);
|
||||
|
||||
// Map state
|
||||
const [basemap, setBasemap] = useState<BasemapId>("osm");
|
||||
const [basemap, setBasemap] = useState<BasemapId>("liberty");
|
||||
const [layerVisibility, setLayerVisibility] = useState<LayerVisibility>(
|
||||
getDefaultVisibility
|
||||
);
|
||||
|
||||
@@ -52,37 +52,61 @@ const LAYER_IDS = {
|
||||
selectionLine: "layer-selection-line",
|
||||
} as const;
|
||||
|
||||
/** Basemap tile definitions */
|
||||
const BASEMAP_TILES: Record<BasemapId, { tiles: string[]; attribution: string; tileSize: number; maxzoom?: number }> = {
|
||||
osm: {
|
||||
tiles: [
|
||||
"https://a.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||
"https://b.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||
"https://c.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||
],
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||
tileSize: 256,
|
||||
/** Basemap definitions — vector style URL or inline raster config */
|
||||
type BasemapDef =
|
||||
| { type: "style"; url: string; maxzoom?: number }
|
||||
| { type: "raster"; tiles: string[]; attribution: string; tileSize: number; maxzoom?: number };
|
||||
|
||||
const BASEMAPS: Record<BasemapId, BasemapDef> = {
|
||||
liberty: {
|
||||
type: "style",
|
||||
url: "https://tiles.openfreemap.org/styles/liberty",
|
||||
},
|
||||
dark: {
|
||||
type: "style",
|
||||
url: "https://tiles.openfreemap.org/styles/dark",
|
||||
},
|
||||
satellite: {
|
||||
type: "raster",
|
||||
tiles: [
|
||||
"https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
|
||||
],
|
||||
attribution: '© <a href="https://www.esri.com">Esri</a>, Maxar, Earthstar Geographics',
|
||||
tileSize: 256,
|
||||
},
|
||||
topo: {
|
||||
tiles: [
|
||||
"https://a.tile.opentopomap.org/{z}/{x}/{y}.png",
|
||||
"https://b.tile.opentopomap.org/{z}/{x}/{y}.png",
|
||||
"https://c.tile.opentopomap.org/{z}/{x}/{y}.png",
|
||||
],
|
||||
attribution:
|
||||
'© <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)',
|
||||
tileSize: 256,
|
||||
maxzoom: 17,
|
||||
orto: {
|
||||
type: "raster",
|
||||
tiles: ["/api/eterra/tiles/orto?z={z}&x={x}&y={y}"],
|
||||
attribution: '© <a href="https://ancpi.ro">ANCPI</a> Ortofoto 2024',
|
||||
tileSize: 512,
|
||||
maxzoom: 19,
|
||||
},
|
||||
};
|
||||
|
||||
function buildStyle(def: BasemapDef): string | maplibregl.StyleSpecification {
|
||||
if (def.type === "style") return def.url;
|
||||
return {
|
||||
version: 8 as const,
|
||||
sources: {
|
||||
basemap: {
|
||||
type: "raster" as const,
|
||||
tiles: def.tiles,
|
||||
tileSize: def.tileSize,
|
||||
attribution: def.attribution,
|
||||
},
|
||||
},
|
||||
layers: [
|
||||
{
|
||||
id: "basemap-tiles",
|
||||
type: "raster" as const,
|
||||
source: "basemap",
|
||||
minzoom: 0,
|
||||
maxzoom: def.maxzoom ?? 19,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Props */
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -135,7 +159,7 @@ export const MapViewer = forwardRef<MapViewerHandle, MapViewerProps>(
|
||||
zoom,
|
||||
martinUrl,
|
||||
className,
|
||||
basemap = "osm",
|
||||
basemap = "liberty",
|
||||
selectionMode = false,
|
||||
onFeatureClick,
|
||||
onSelectionChange,
|
||||
@@ -236,33 +260,14 @@ export const MapViewer = forwardRef<MapViewerHandle, MapViewerProps>(
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
const initialBasemap = BASEMAP_TILES[basemap];
|
||||
const basemapDef = BASEMAPS[basemap];
|
||||
|
||||
const map = new maplibregl.Map({
|
||||
container: containerRef.current,
|
||||
style: {
|
||||
version: 8,
|
||||
sources: {
|
||||
basemap: {
|
||||
type: "raster",
|
||||
tiles: initialBasemap.tiles,
|
||||
tileSize: initialBasemap.tileSize,
|
||||
attribution: initialBasemap.attribution,
|
||||
},
|
||||
},
|
||||
layers: [
|
||||
{
|
||||
id: "basemap-tiles",
|
||||
type: "raster",
|
||||
source: "basemap",
|
||||
minzoom: 0,
|
||||
maxzoom: initialBasemap.maxzoom ?? 19,
|
||||
},
|
||||
],
|
||||
},
|
||||
style: buildStyle(basemapDef),
|
||||
center: center ?? DEFAULT_CENTER,
|
||||
zoom: zoom ?? DEFAULT_ZOOM,
|
||||
maxZoom: initialBasemap.maxzoom ?? 20,
|
||||
maxZoom: basemapDef.maxzoom ?? 20,
|
||||
});
|
||||
|
||||
mapRef.current = map;
|
||||
|
||||
@@ -46,7 +46,7 @@ export type MapViewState = {
|
||||
/* Basemap */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
export type BasemapId = "osm" | "satellite" | "topo";
|
||||
export type BasemapId = "liberty" | "dark" | "satellite" | "orto";
|
||||
|
||||
export type BasemapDef = {
|
||||
id: BasemapId;
|
||||
|
||||
Reference in New Issue
Block a user