fix: OSINT 기사 중복 수집 방지 + MapLibre symbol layer race condition 해소
- OsintCollector: title 기반 24h 중복 체크 추가 (GDELT/Google News) - ShipLayer: hover를 feature-state로 분리하여 setData 빈도 감소 - ShipLayer: ships-korean-label 조건부 마운트 → visibility 제어로 변경
This commit is contained in:
부모
4dd1597111
커밋
e304a841ed
@ -118,6 +118,8 @@ public class OsintCollector {
|
||||
if (articleUrl == null || title == null || title.isBlank()) continue;
|
||||
|
||||
if (osintFeedRepository.existsBySourceAndSourceUrl("gdelt", articleUrl)) continue;
|
||||
if (osintFeedRepository.existsByRegionAndTitleAndCollectedAtAfter(
|
||||
region, title, Instant.now().minus(24, ChronoUnit.HOURS))) continue;
|
||||
|
||||
String seendate = article.path("seendate").asText(null);
|
||||
Instant publishedAt = parseGdeltDate(seendate);
|
||||
@ -182,6 +184,8 @@ public class OsintCollector {
|
||||
|
||||
if (link == null || title == null || title.isBlank()) continue;
|
||||
if (osintFeedRepository.existsBySourceAndSourceUrl(sourceName, link)) continue;
|
||||
if (osintFeedRepository.existsByRegionAndTitleAndCollectedAtAfter(
|
||||
region, title, Instant.now().minus(24, ChronoUnit.HOURS))) continue;
|
||||
|
||||
Instant publishedAt = parseRssDate(pubDate);
|
||||
|
||||
|
||||
@ -9,5 +9,7 @@ public interface OsintFeedRepository extends JpaRepository<OsintFeed, Long> {
|
||||
|
||||
boolean existsBySourceAndSourceUrl(String source, String sourceUrl);
|
||||
|
||||
boolean existsByRegionAndTitleAndCollectedAtAfter(String region, String title, Instant since);
|
||||
|
||||
List<OsintFeed> findByRegionAndCollectedAtAfterOrderByPublishedAtDesc(String region, Instant since);
|
||||
}
|
||||
|
||||
@ -359,6 +359,7 @@ export function ShipLayer({ ships, militaryOnly, koreanOnly, hoveredMmsi, focusM
|
||||
const [selectedMmsi, setSelectedMmsi] = useState<string | null>(null);
|
||||
const [imageReady, setImageReady] = useState(false);
|
||||
const highlightKorean = !!koreanOnly;
|
||||
const prevHoveredRef = useRef<string | null>(null);
|
||||
|
||||
// focusMmsi로 외부에서 모달 열기
|
||||
useEffect(() => {
|
||||
@ -399,7 +400,6 @@ export function ShipLayer({ ships, militaryOnly, koreanOnly, hoveredMmsi, focusM
|
||||
isMil: isMilitary(ship.category) ? 1 : 0,
|
||||
isKorean: ship.flag === 'KR' ? 1 : 0,
|
||||
isCheonghae: ship.mmsi === '440001981' ? 1 : 0,
|
||||
isHovered: ship.mmsi === hoveredMmsi ? 1 : 0,
|
||||
heading: ship.heading,
|
||||
},
|
||||
geometry: {
|
||||
@ -408,7 +408,26 @@ export function ShipLayer({ ships, militaryOnly, koreanOnly, hoveredMmsi, focusM
|
||||
},
|
||||
}));
|
||||
return { type: 'FeatureCollection' as const, features };
|
||||
}, [filtered, hoveredMmsi]);
|
||||
}, [filtered]);
|
||||
|
||||
// hoveredMmsi 변경 시 feature-state로 hover 표시 (GeoJSON 재생성 없이)
|
||||
useEffect(() => {
|
||||
if (!map) return;
|
||||
const m = map.getMap();
|
||||
if (!m.getSource('ships-source')) return;
|
||||
|
||||
if (prevHoveredRef.current != null) {
|
||||
try {
|
||||
m.removeFeatureState({ source: 'ships-source', id: prevHoveredRef.current });
|
||||
} catch { /* source not ready */ }
|
||||
}
|
||||
if (hoveredMmsi) {
|
||||
try {
|
||||
m.setFeatureState({ source: 'ships-source', id: hoveredMmsi }, { hovered: true });
|
||||
} catch { /* source not ready */ }
|
||||
}
|
||||
prevHoveredRef.current = hoveredMmsi ?? null;
|
||||
}, [map, hoveredMmsi]);
|
||||
|
||||
// Register click and cursor handlers
|
||||
useEffect(() => {
|
||||
@ -447,12 +466,12 @@ export function ShipLayer({ ships, militaryOnly, koreanOnly, hoveredMmsi, focusM
|
||||
|
||||
return (
|
||||
<>
|
||||
<Source id="ships-source" type="geojson" data={shipGeoJson}>
|
||||
<Source id="ships-source" type="geojson" data={shipGeoJson} promoteId="mmsi">
|
||||
{/* Hovered ship highlight ring */}
|
||||
<Layer
|
||||
id="ships-hover-ring"
|
||||
type="circle"
|
||||
filter={['==', ['get', 'isHovered'], 1]}
|
||||
filter={['boolean', ['feature-state', 'hovered'], false]}
|
||||
paint={{
|
||||
'circle-radius': 18,
|
||||
'circle-color': 'rgba(255, 255, 255, 0.1)',
|
||||
@ -474,13 +493,13 @@ export function ShipLayer({ ships, militaryOnly, koreanOnly, hoveredMmsi, focusM
|
||||
'circle-stroke-opacity': highlightKorean ? 1 : 0.6,
|
||||
}}
|
||||
/>
|
||||
{/* Korean ship label (only when highlighted) */}
|
||||
{highlightKorean && (
|
||||
{/* Korean ship label — always mounted, visibility으로 제어 */}
|
||||
<Layer
|
||||
id="ships-korean-label"
|
||||
type="symbol"
|
||||
filter={['==', ['get', 'isKorean'], 1]}
|
||||
layout={{
|
||||
'visibility': highlightKorean ? 'visible' : 'none',
|
||||
'text-field': ['get', 'name'],
|
||||
'text-size': 9,
|
||||
'text-offset': [0, 2.2],
|
||||
@ -494,7 +513,6 @@ export function ShipLayer({ ships, militaryOnly, koreanOnly, hoveredMmsi, focusM
|
||||
'text-halo-width': 1,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/* Main ship triangles */}
|
||||
<Layer
|
||||
id="ships-triangles"
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user