From 8d5316dd1b906b8c766f6996897e3b91c1ee675d Mon Sep 17 00:00:00 2001 From: Claude VM Date: Wed, 20 May 2026 09:08:41 +0300 Subject: [PATCH] feat(geoportal-v2): map styling parity with eterra.live MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Marius didn't like the previous map palette + missing labels. Re-paint to match eterra.live's V2 map: UATs: Was: #9ca3af / #6b7280 (two-tone gray) Now: #7c3aed violet, width ramps 0.3 → 0.6 by zoom band Plus new v2-uats-z8-label symbol layer that prints the UAT name at z9–12 (Noto Sans 10px, violet text + white halo). Parcele (terenuri): Was: #0ea5e9 sky-blue line, transparent 0.001 fill (hit-only) Now: #22c55e green fill at 0.15 opacity (so the click area is visible at the same time it acts as hit-target) + #15803d darker green outline at 0.8 / 0.9 opacity. Cadastral-ref label layer renders at z17+ (small enough zoom that the polygon can carry a 10px label without overlapping neighbours). Cladiri (buildings): Was: #fb923c orange / #ea580c dark orange Now: #3b82f6 blue fill at 0.5 / #1e3a5f navy line — same blue eterra.live uses, distinct from the green parcel underlay and from the violet UATs. Cladiri „fără acte" (build_legal == 0): NEW. Two amber overlay layers, filtered on the tile's build_legal property: v2-cladiri-unreg-fill — #f59e0b at 0.6 opacity v2-cladiri-unreg-line — #b45309 at width 1 Renders on top of the default cladiri layers so illegal builds visibly pop. If a PMTiles tile doesn't carry build_legal yet the layer is empty — no regression on legal buildings. Building suffix label (C1, C2, C3…): NEW. v2-cladiri-label symbol at z16+. Mirrors eterra.live's expression: extract the slice after the last "-" of cadastral_ref ("354686-C1" → "C1"). Noto Sans 9px, navy text + white halo so it reads on both light and dark basemaps. Click handler unchanged — v2-cladiri-fill covers ALL buildings (no filter), so legal vs unreg both route through the same query. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/modules/geoportal/v2/map-viewer.tsx | 184 ++++++++++++++++++++---- 1 file changed, 155 insertions(+), 29 deletions(-) diff --git a/src/modules/geoportal/v2/map-viewer.tsx b/src/modules/geoportal/v2/map-viewer.tsx index e9ce1fb..d4d7e17 100644 --- a/src/modules/geoportal/v2/map-viewer.tsx +++ b/src/modules/geoportal/v2/map-viewer.tsx @@ -116,7 +116,10 @@ export const MapViewer = forwardRef(function MapViewer( url: `pmtiles://${PMTILES_URL}`, }); - // UAT boundaries — visible at every zoom, more prominent on low/mid zoom + // ── UAT boundaries ── + // Palette mirrors eterra.live's map: violet #7c3aed at increasing + // line-width per zoom band so the boundary stays visible from + // country view to street view without overpowering parcels. map.addLayer({ id: "v2-uats-z5", type: "line", @@ -125,9 +128,9 @@ export const MapViewer = forwardRef(function MapViewer( minzoom: 0, maxzoom: 8, paint: { - "line-color": "#9ca3af", - "line-width": 0.8, - "line-opacity": 0.6, + "line-color": "#7c3aed", + "line-width": 0.3, + "line-opacity": 0.7, }, }); map.addLayer({ @@ -138,13 +141,86 @@ export const MapViewer = forwardRef(function MapViewer( minzoom: 8, maxzoom: 13, paint: { - "line-color": "#6b7280", - "line-width": 1.2, - "line-opacity": 0.7, + "line-color": "#7c3aed", + "line-width": 0.6, + "line-opacity": 0.85, + }, + }); + // UAT name label at z9–12 (when the boundary is the main feature) + map.addLayer({ + id: "v2-uats-z8-label", + type: "symbol", + source: PM_SRC, + "source-layer": "gis_uats_z8", + minzoom: 9, + maxzoom: 13, + layout: { + "text-field": ["coalesce", ["get", "name"], ""], + "text-font": ["Noto Sans Regular"], + "text-size": 10, + "text-anchor": "center", + "text-allow-overlap": false, + }, + paint: { + "text-color": "#5b21b6", + "text-halo-color": "#ffffff", + "text-halo-width": 1.5, }, }); - // Buildings — fill + outline + // ── Parcele (terenuri) ── + // Green fill (#22c55e/0.15) + dark green stroke (#15803d/0.8). + // Fill provides the click-hit area without needing a separate + // transparent layer; the low opacity keeps overlapping buildings + // legible underneath. + map.addLayer({ + id: "v2-terenuri-hit", + type: "fill", + source: PM_SRC, + "source-layer": "gis_terenuri", + minzoom: 13, + paint: { + "fill-color": "#22c55e", + "fill-opacity": 0.15, + }, + }); + map.addLayer({ + id: "v2-terenuri-line", + type: "line", + source: PM_SRC, + "source-layer": "gis_terenuri", + minzoom: 13, + paint: { + "line-color": "#15803d", + "line-width": 0.8, + "line-opacity": 0.9, + }, + }); + // Parcel cadastral ref label — z17+ where the polygon is big + // enough that the label doesn't clutter. + map.addLayer({ + id: "v2-terenuri-label", + type: "symbol", + source: PM_SRC, + "source-layer": "gis_terenuri", + minzoom: 17, + layout: { + "text-field": ["coalesce", ["get", "cadastral_ref"], ""], + "text-font": ["Noto Sans Regular"], + "text-size": 10, + "text-anchor": "center", + "text-allow-overlap": false, + "text-max-width": 8, + }, + paint: { + "text-color": "#14532d", + "text-halo-color": "#ffffff", + "text-halo-width": 1.5, + }, + }); + + // ── Cladiri (buildings) ── + // Default blue (#3b82f6/0.5 fill + #1e3a5f/0.6 stroke) at z13+. map.addLayer({ id: "v2-cladiri-fill", type: "fill", @@ -152,8 +228,8 @@ export const MapViewer = forwardRef(function MapViewer( "source-layer": "gis_cladiri", minzoom: 13, paint: { - "fill-color": "#fb923c", - "fill-opacity": 0.25, + "fill-color": "#3b82f6", + "fill-opacity": 0.5, }, }); map.addLayer({ @@ -163,34 +239,84 @@ export const MapViewer = forwardRef(function MapViewer( "source-layer": "gis_cladiri", minzoom: 13, paint: { - "line-color": "#ea580c", + "line-color": "#1e3a5f", "line-width": 0.6, }, }); - // Parcels (terenuri) — line outline only + // ── Cladiri „fără acte" (build_legal == 0) ── + // Amber overlay (#f59e0b/0.6) + darker stroke so illegal / + // un-registered buildings pop visibly against the default blue. + // The layer renders only when the tile property `build_legal` is + // present; PMTiles overview built without that property simply + // shows nothing here (no overlap on legal buildings either). map.addLayer({ - id: "v2-terenuri-line", - type: "line", - source: PM_SRC, - "source-layer": "gis_terenuri", - minzoom: 13, - paint: { - "line-color": "#0ea5e9", - "line-width": 0.8, - "line-opacity": 0.85, - }, - }); - // Invisible fill for click hit-testing - map.addLayer({ - id: "v2-terenuri-hit", + id: "v2-cladiri-unreg-fill", type: "fill", source: PM_SRC, - "source-layer": "gis_terenuri", + "source-layer": "gis_cladiri", minzoom: 13, + filter: ["==", ["get", "build_legal"], 0], paint: { - "fill-color": "#0ea5e9", - "fill-opacity": 0.001, + "fill-color": "#f59e0b", + "fill-opacity": 0.6, + }, + }); + map.addLayer({ + id: "v2-cladiri-unreg-line", + type: "line", + source: PM_SRC, + "source-layer": "gis_cladiri", + minzoom: 13, + filter: ["==", ["get", "build_legal"], 0], + paint: { + "line-color": "#b45309", + "line-width": 1, + "line-opacity": 0.9, + }, + }); + + // Building suffix label — "354686-C1" → "C1". Same logic eterra.live + // uses: slice after the last dash. z16+ so we don't clutter the + // overview when zoomed out. + map.addLayer({ + id: "v2-cladiri-label", + type: "symbol", + source: PM_SRC, + "source-layer": "gis_cladiri", + minzoom: 16, + layout: { + "text-field": [ + "case", + ["has", "cadastral_ref"], + [ + "let", + "ref", + ["get", "cadastral_ref"], + [ + "let", + "dashIdx", + ["index-of", "-", ["var", "ref"]], + [ + "case", + [">=", ["var", "dashIdx"], 0], + ["slice", ["var", "ref"], ["+", ["var", "dashIdx"], 1]], + ["var", "ref"], + ], + ], + ], + "", + ], + "text-font": ["Noto Sans Regular"], + "text-size": 9, + "text-anchor": "center", + "text-allow-overlap": false, + "text-max-width": 6, + }, + paint: { + "text-color": "#1e3a5f", + "text-halo-color": "#ffffff", + "text-halo-width": 1.2, }, }); }, []);