101 lines
2.8 KiB
TypeScript
101 lines
2.8 KiB
TypeScript
|
|
// app/admin/upload/_components/map/GeoPreview.tsx
|
||
|
|
"use client";
|
||
|
|
|
||
|
|
import React, { useEffect, useRef } from "react";
|
||
|
|
import Map from "ol/Map";
|
||
|
|
import View from "ol/View";
|
||
|
|
import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer";
|
||
|
|
import { OSM } from "ol/source";
|
||
|
|
import VectorSource from "ol/source/Vector";
|
||
|
|
import WKT from "ol/format/WKT";
|
||
|
|
import { Circle as CircleStyle, Fill, Stroke, Style } from "ol/style";
|
||
|
|
import { fromLonLat } from "ol/proj";
|
||
|
|
|
||
|
|
const GeoPreview = ({ features }: { features: any[] }) => {
|
||
|
|
const mapRef = useRef<HTMLDivElement>(null);
|
||
|
|
const mapObj = useRef<Map | null>(null);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (!features || features.length === 0 || !mapRef.current) return;
|
||
|
|
|
||
|
|
// Bersihkan map lama jika ada re-render strict mode
|
||
|
|
if (mapObj.current) {
|
||
|
|
mapObj.current.setTarget(undefined);
|
||
|
|
}
|
||
|
|
|
||
|
|
const wktFormat = new WKT();
|
||
|
|
const vectorSource = new VectorSource();
|
||
|
|
|
||
|
|
features.forEach((item) => {
|
||
|
|
try {
|
||
|
|
const feature = wktFormat.readFeature(item.geometry, {
|
||
|
|
dataProjection: `EPSG:4326`,
|
||
|
|
featureProjection: "EPSG:3857",
|
||
|
|
});
|
||
|
|
vectorSource.addFeature(feature);
|
||
|
|
} catch (err) {
|
||
|
|
console.error("WKT parse error:", err);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
const vectorLayer = new VectorLayer({
|
||
|
|
source: vectorSource,
|
||
|
|
style: (feature) => {
|
||
|
|
const type = feature.getGeometry()?.getType();
|
||
|
|
|
||
|
|
if (type === "Polygon" || type === "MultiPolygon") {
|
||
|
|
return new Style({
|
||
|
|
fill: new Fill({ color: "rgba(0, 153, 255, 0.4)" }),
|
||
|
|
stroke: new Stroke({ color: "#0099ff", width: 2 }),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (type === "LineString" || type === "MultiLineString") {
|
||
|
|
return new Style({
|
||
|
|
stroke: new Stroke({ color: "#0099ff", width: 3 }),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (type === "Point" || type === "MultiPoint") {
|
||
|
|
return new Style({
|
||
|
|
image: new CircleStyle({
|
||
|
|
radius: 6,
|
||
|
|
fill: new Fill({ color: "#0099ff" }),
|
||
|
|
stroke: new Stroke({ color: "#ffffff", width: 2 }),
|
||
|
|
}),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
mapObj.current = new Map({
|
||
|
|
target: mapRef.current,
|
||
|
|
layers: [
|
||
|
|
new TileLayer({ source: new OSM() }),
|
||
|
|
vectorLayer,
|
||
|
|
],
|
||
|
|
view: new View({
|
||
|
|
center: fromLonLat([110, -6]),
|
||
|
|
zoom: 5,
|
||
|
|
}),
|
||
|
|
});
|
||
|
|
|
||
|
|
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);
|
||
|
|
};
|
||
|
|
}, [features]);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
ref={mapRef}
|
||
|
|
style={{ width: "100%", height: "100%", minHeight: "400px", border: "1px solid #ccc", borderRadius: "8px" }}
|
||
|
|
/>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
export default GeoPreview;
|