147 lines
4.4 KiB
TypeScript
147 lines
4.4 KiB
TypeScript
|
|
// app/admin/upload/_components/map/StylePreview.tsx
|
||
|
|
"use client";
|
||
|
|
|
||
|
|
import React, { useEffect, useRef } from "react";
|
||
|
|
import Map from "ol/Map";
|
||
|
|
import View from "ol/View";
|
||
|
|
import VectorLayer from "ol/layer/Vector";
|
||
|
|
import VectorSource from "ol/source/Vector";
|
||
|
|
import TileLayer from "ol/layer/Tile";
|
||
|
|
import OSM from "ol/source/OSM";
|
||
|
|
import WKT from "ol/format/WKT";
|
||
|
|
import Feature from "ol/Feature";
|
||
|
|
|
||
|
|
import Style from "ol/style/Style";
|
||
|
|
import Fill from "ol/style/Fill";
|
||
|
|
import Stroke from "ol/style/Stroke";
|
||
|
|
import CircleStyle from "ol/style/Circle";
|
||
|
|
import { defaults as defaultControls } from 'ol/control';
|
||
|
|
|
||
|
|
// Import Geostyler dynamic/client side only
|
||
|
|
import SldStyleParser from "geostyler-sld-parser";
|
||
|
|
import OlStyleParser from "geostyler-openlayers-parser";
|
||
|
|
|
||
|
|
// ... (Kode helper parseWKT, normalizeKey, createFeatures sama seperti sebelumnya) ...
|
||
|
|
const wkt = new WKT();
|
||
|
|
|
||
|
|
function parseWKT(str: string) {
|
||
|
|
try {
|
||
|
|
return wkt.readGeometry(str, {
|
||
|
|
dataProjection: "EPSG:4326",
|
||
|
|
featureProjection: "EPSG:3857",
|
||
|
|
});
|
||
|
|
} catch (e) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function normalizeKey(key: string) {
|
||
|
|
return key.replace(/[^a-zA-Z0-9_]/g, "_");
|
||
|
|
}
|
||
|
|
|
||
|
|
function createFeatures(data: any[]) {
|
||
|
|
if (!data) return [];
|
||
|
|
return data.map((row) => {
|
||
|
|
const geometry = parseWKT(row.geometry);
|
||
|
|
if (!geometry) return null; // Handle invalid geometry
|
||
|
|
|
||
|
|
const feat = new Feature();
|
||
|
|
Object.entries(row).forEach(([key, value]) => {
|
||
|
|
if (key === "geometry") return;
|
||
|
|
feat.set(key, value);
|
||
|
|
const normalized = normalizeKey(key).toUpperCase();
|
||
|
|
if (normalized !== key) {
|
||
|
|
feat.set(normalized, value);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
feat.setGeometry(geometry);
|
||
|
|
return feat;
|
||
|
|
}).filter((f): f is Feature => f !== null);
|
||
|
|
}
|
||
|
|
|
||
|
|
const defaultStyle = new Style({
|
||
|
|
image: new CircleStyle({
|
||
|
|
radius: 4,
|
||
|
|
fill: new Fill({ color: "#3388ff" }),
|
||
|
|
stroke: new Stroke({ color: "#333", width: 1 }),
|
||
|
|
}),
|
||
|
|
stroke: new Stroke({ color: "#3388ff", width: 2 }),
|
||
|
|
fill: new Fill({ color: "rgba(51,136,255,0.5)" }),
|
||
|
|
});
|
||
|
|
|
||
|
|
const SpatialStylePreview = ({ data, styleConfig }: { data: any[], styleConfig: any }) => {
|
||
|
|
const mapRef = useRef<HTMLDivElement>(null);
|
||
|
|
const mapObj = useRef<Map | null>(null);
|
||
|
|
const vectorLayer = useRef<VectorLayer<VectorSource> | null>(null);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (!mapRef.current || !data) return;
|
||
|
|
|
||
|
|
if (mapObj.current) mapObj.current.setTarget(undefined);
|
||
|
|
|
||
|
|
const features = createFeatures(data);
|
||
|
|
const vectorSource = new VectorSource({ features });
|
||
|
|
|
||
|
|
vectorLayer.current = new VectorLayer({
|
||
|
|
source: vectorSource,
|
||
|
|
style: defaultStyle,
|
||
|
|
});
|
||
|
|
|
||
|
|
mapObj.current = new Map({
|
||
|
|
target: mapRef.current,
|
||
|
|
controls: defaultControls({ attribution: false, zoom: true }),
|
||
|
|
layers: [
|
||
|
|
new TileLayer({ source: new OSM() }),
|
||
|
|
vectorLayer.current,
|
||
|
|
],
|
||
|
|
view: new View({
|
||
|
|
center: [12600000, -830000], // Sesuaikan center default Indonesia
|
||
|
|
zoom: 5,
|
||
|
|
}),
|
||
|
|
});
|
||
|
|
|
||
|
|
// Auto zoom to extent
|
||
|
|
const extent = vectorSource.getExtent();
|
||
|
|
if (extent && extent[0] !== Infinity) {
|
||
|
|
mapObj.current.getView().fit(extent, { padding: [20, 20, 20, 20] });
|
||
|
|
}
|
||
|
|
|
||
|
|
return () => { if (mapObj.current) mapObj.current.setTarget(undefined); };
|
||
|
|
}, [data]);
|
||
|
|
|
||
|
|
// Apply SLD Logic
|
||
|
|
useEffect(() => {
|
||
|
|
if (!vectorLayer.current) return;
|
||
|
|
if (!styleConfig || styleConfig.styleType !== "sld") {
|
||
|
|
vectorLayer.current.setStyle(defaultStyle);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const applySLD = async () => {
|
||
|
|
try {
|
||
|
|
const sldParser = new SldStyleParser();
|
||
|
|
const olParser = new OlStyleParser();
|
||
|
|
const sldResult = await sldParser.readStyle(styleConfig.sldContent);
|
||
|
|
if (!sldResult.output) throw new Error("Empty style");
|
||
|
|
|
||
|
|
const olResult = await olParser.writeStyle(sldResult.output);
|
||
|
|
const olStyle = olResult.output;
|
||
|
|
|
||
|
|
if (typeof olStyle === "function") {
|
||
|
|
vectorLayer.current?.setStyle((f, r) => olStyle(f, r));
|
||
|
|
} else {
|
||
|
|
vectorLayer.current?.setStyle(olStyle);
|
||
|
|
}
|
||
|
|
vectorLayer.current?.getSource()?.changed();
|
||
|
|
} catch (err) {
|
||
|
|
console.warn("SLD parsing failed", err);
|
||
|
|
vectorLayer.current?.setStyle(defaultStyle);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
applySLD();
|
||
|
|
}, [styleConfig]);
|
||
|
|
|
||
|
|
return <div ref={mapRef} className="w-full h-full rounded-lg border shadow-sm min-h-[400px]" />;
|
||
|
|
};
|
||
|
|
|
||
|
|
export default SpatialStylePreview;
|