diff --git a/src/components/MapTiff/MapTiff.tsx b/src/components/MapTiff/MapTiff.tsx index 51f940c..5107541 100644 --- a/src/components/MapTiff/MapTiff.tsx +++ b/src/components/MapTiff/MapTiff.tsx @@ -12,8 +12,7 @@ import { } from "@/utils/constants"; import MapPopup from "../MapPopup/MapPopup"; import { MapTiffContext } from "@/contexts/MapContext"; - -const HOST_URL = process.env.NEXT_PUBLIC_HOST_URL; +import { fetchMapURL } from "@/services/mapServices"; const MapTiff = ({ isReduced = false, ...props }: { isReduced?: boolean }) => { const { @@ -25,6 +24,7 @@ const MapTiff = ({ isReduced = false, ...props }: { isReduced?: boolean }) => { setDescRetracted, } = useContext(MapTiffContext); + const [loadedSources, setLoadedSources] = useState(new Set()); const { id, year } = currentVisu; const [map, setMap] = useState(null); const mapContainer = useRef(null); @@ -32,108 +32,52 @@ const MapTiff = ({ isReduced = false, ...props }: { isReduced?: boolean }) => { new maplibregl.Popup({ closeButton: false, closeOnClick: false }), ); - const initializeMap = useCallback(() => { - if (mapContainer.current) { - const newMap = new maplibregl.Map({ - container: mapContainer.current!, - style: MAP_TIFF_STYLE, - center: isReduced ? [-54.69, -15.13] : [-51.55, -15], - zoom: isReduced ? 3 : 3.6, - minZoom: isReduced ? 3 : 3.6, - maxZoom: 10, - }); - - newMap.on("load", () => { - if (!isReduced) { - newMap.addControl(new maplibregl.NavigationControl(), "bottom-left"); - } - - newMap.addSource("brazil-states", { - type: "geojson", - data: MAP_TIFF_BRAZIL_STATES, - }); - - newMap.addLayer({ - id: "state-fills", - type: "fill", - source: "brazil-states", - layout: {}, - paint: { - "fill-color": "black", - "fill-opacity": [ - "case", - ["boolean", ["feature-state", "hover"], false], - 0.3, - 0, - ], - }, - }); - - newMap.addLayer({ - id: "state-borders", - type: "line", - source: "brazil-states", - layout: {}, - paint: { - "line-color": "#2D2D2D", - "line-width": [ - "case", - ["boolean", ["feature-state", "hover"], false], - 2.5, - 1.5, - ], - }, - }); - - newMap.addSource("brazil-cities", { - type: "geojson", - data: MAP_TIFF_BRAZIL_CITIES, - }); - - newMap.addLayer({ - id: "brazil-cities", - type: "line", - source: "brazil-cities", - layout: {}, - paint: { - "line-color": "#00000050", - "line-width": 2, - }, - minzoom: 6, - }); - }); - - setMap(newMap); + const showOcaLayer = (map: maplibregl.Map, sourceKey: string) => { + const symbolLayer = map + .getStyle() + ?.layers?.find((layer) => layer.type === "symbol"); + + if (map.getSource(sourceKey) && symbolLayer) { + map.addLayer( + { + type: "raster", + source: sourceKey, + id: sourceKey, + }, + symbolLayer?.id, + ); } - }, [isReduced, mapContainer]); + }; - const loadSource = useCallback( - async (id: string, year: string) => { - if (!map?.getSource(id + year) && tiffs.length > 0) { - const mapData = tiffs.find((data) => data.fields.id === id)?.fields; - const body = JSON.stringify(mapData); - - // Fetch the raster layer from the ee API - const response = await fetch( - `${HOST_URL}/api/ee?name=${id}&year=${year}`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body, - }, - ); - - if (response.status !== 200) { - setLoading(false); - - return; - } + const cleanOcaLayers = (map: maplibregl.Map) => { + const mapLayers: string[] = map + ?.getLayersOrder() + .filter((layer: string) => layer.startsWith("@oca/")); + mapLayers.forEach((layer: string) => { + if (map?.getLayer(layer)) { + map?.removeLayer(layer); + } + }); + }; + + const loadAdditionalSources = useCallback( + async (map: maplibregl.Map) => { + const yearStr = year || "general"; + for (const additionalMapData of tiffs) { + for (const newYear of Object.keys(additionalMapData.fields.imageData)) { + const newId = additionalMapData.fields.id; + const sourceKey = `@oca/${newId}${newYear}`; + if (map.getSource(sourceKey) || (newId === id && newYear === yearStr)) + continue; + + const { url } = await fetchMapURL( + newId, + newYear, + JSON.stringify(additionalMapData.fields), + ); - const { url } = await response.json(); - if (!map?.getSource(id + year)) { - map?.addSource(id + year, { + // Adiciona a nova fonte ao mapa + map.addSource(sourceKey, { type: "raster", tiles: [url], tileSize: isReduced ? 64 : 128, @@ -141,32 +85,78 @@ const MapTiff = ({ isReduced = false, ...props }: { isReduced?: boolean }) => { } } }, - [map, tiffs, setLoading, isReduced], + [tiffs, id, year, isReduced], ); - const loadMapLayer = useCallback( - async (id: string, year: string) => { - setLoading(true); - await loadSource(id, year); + const loadInitialSources = useCallback( + async (map: maplibregl.Map) => { + // === Add current raster source + const yearStr = year ? year : "general"; + const mapData = tiffs.find((data) => data.fields.id === id)?.fields; + const { url } = await fetchMapURL(id, yearStr, JSON.stringify(mapData)); + map.addSource(`@oca/${id}${yearStr}`, { + type: "raster", + tiles: [url], + tileSize: isReduced ? 64 : 128, + }); - const symbolLayer = map - ?.getStyle() - ?.layers?.find((layer) => layer.type === "symbol"); + // === Add Brazil states source + map.addSource("brazil-states", { + type: "geojson", + data: MAP_TIFF_BRAZIL_STATES, + }); - if (map && symbolLayer) { - map.addLayer( - { - type: "raster", - source: `${id}${year}`, - id: `@oca/${id}${year}`, - }, - symbolLayer.id, - ); - } + map.addLayer({ + id: "state-fills", + type: "fill", + source: "brazil-states", + layout: {}, + paint: { + "fill-color": "black", + "fill-opacity": [ + "case", + ["boolean", ["feature-state", "hover"], false], + 0.3, + 0, + ], + }, + }); - setLoading(false); + map.addLayer({ + id: "state-borders", + type: "line", + source: "brazil-states", + layout: {}, + paint: { + "line-color": "#2D2D2D", + "line-width": [ + "case", + ["boolean", ["feature-state", "hover"], false], + 2.5, + 1.5, + ], + }, + }); + + // === Add Brazil cities source + map.addSource("brazil-cities", { + type: "geojson", + data: MAP_TIFF_BRAZIL_CITIES, + }); + + map.addLayer({ + id: "brazil-cities", + type: "line", + source: "brazil-cities", + layout: {}, + paint: { + "line-color": "#00000050", + "line-width": 2, + }, + minzoom: 6, + }); }, - [map, setLoading, loadSource], + [id, year, tiffs, isReduced], ); const addPopupEffect = useCallback( @@ -191,37 +181,35 @@ const MapTiff = ({ isReduced = false, ...props }: { isReduced?: boolean }) => { ); const fcProperties = e.features[0].properties; - if (!fcProperties[id + year]) popupRef.current.remove(); - else { - const fcMetadata = JSON.parse(fcProperties[id + year]); - const rasterMetadata = tiffs.filter( - (data) => data.fields.id === id, - )[0]; - - // const rasterColors = rasterMetadata.fields.imageData[ - // year - // ]?.imageParams.map((param: any) => param.color); - const rasterColors = - id === "cisterna" - ? [] - : rasterMetadata.fields.imageData[year]?.imageParams.map( - (param) => param.color, - ); - - root.render( - , - ); + const fcMetadata = JSON.parse(fcProperties[id + year]); + const rasterMetadata = tiffs.filter( + (data) => data.fields.id === id, + )[0]; + + // === Refact this in the future + // const rasterColors = rasterMetadata.fields.imageData[ + // year + // ]?.imageParams.map((param: any) => param.color); + const rasterColors = + id === "cisterna" + ? [] + : rasterMetadata.fields.imageData[year]?.imageParams.map( + (param) => param.color, + ); + + root.render( + , + ); - popupRef.current - .setLngLat(e.lngLat) - .setDOMContent(popupContainer) - .addTo(map); - } + popupRef.current + .setLngLat(e.lngLat) + .setDOMContent(popupContainer) + .addTo(map); } }); @@ -239,35 +227,64 @@ const MapTiff = ({ isReduced = false, ...props }: { isReduced?: boolean }) => { [map, tiffs], ); - const cleanOcaLayers = (map: maplibregl.Map) => { - const mapLayers: string[] = map - ?.getLayersOrder() - .filter((layer: string) => layer.startsWith("@oca/")); - mapLayers.forEach((layer: string) => { - if (map?.getLayer(layer)) { - map?.removeLayer(layer); - } - }); - }; + const initializeMap = useCallback(() => { + if (mapContainer.current) { + const newMap = new maplibregl.Map({ + container: mapContainer.current!, + style: MAP_TIFF_STYLE, + center: isReduced ? [-54.69, -15.13] : [-51.55, -15], + zoom: isReduced ? 3 : 3.6, + minZoom: isReduced ? 3 : 3.6, + maxZoom: 10, + }); - useEffect(() => { - if (map && id && year) { - if (!isReduced) addPopupEffect(id, year); - loadMapLayer(id, year); + newMap.on("load", async () => { + if (!isReduced) + newMap.addControl(new maplibregl.NavigationControl(), "bottom-left"); - return () => { - cleanOcaLayers(map); - }; + loadInitialSources(newMap); + loadAdditionalSources(newMap); + }); + + newMap.on("sourcedata", (e) => { + if (e.sourceId && e.isSourceLoaded && !loadedSources.has(e.sourceId)) { + setLoadedSources((prev) => { + const newSet = new Set(prev.add(e.sourceId)); + newSet.add(e.sourceId); + + return newSet; + }); + } + }); + + setMap(newMap); } + }, [ + isReduced, + mapContainer, + loadedSources, + loadInitialSources, + loadAdditionalSources, + ]); + + useEffect(() => { + const yearStr = year || "general"; + const sourceKey = `@oca/${id}${yearStr}`; + if (loadedSources.has(sourceKey) && map && !map.getLayer(sourceKey)) { + setLoading(true); + cleanOcaLayers(map); + if (!isReduced) addPopupEffect(id, yearStr); + showOcaLayer(map, sourceKey); + setLoading(false); + } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [id, year, map]); + }, [id, year, loadedSources]); useEffect(() => { - if (!map) { - initializeMap(); - } - }, [map, tiffs, isReduced, initializeMap]); + initializeMap(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const retrieveModals = useCallback(() => { setMenuRetracted(true); diff --git a/src/services/mapServices.ts b/src/services/mapServices.ts new file mode 100644 index 0000000..8edfb77 --- /dev/null +++ b/src/services/mapServices.ts @@ -0,0 +1,21 @@ +const HOST_URL = process.env.NEXT_PUBLIC_HOST_URL; + +export async function fetchMapURL(id: string, year: string, body: BodyInit) { + try { + const response = await fetch(`${HOST_URL}/api/ee?name=${id}&year=${year}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body, + }); + + const data = await response.json(); + + return data; + } catch (error) { + console.error("Erro ao buscar fontes de mapa.", error); + + return {}; + } +}