Merge pull request 'feat: 어구 마름모 아이콘 + 리플레이 모선 색상 구분' (#194) from feature/gear-diamond-icon into develop
This commit is contained in:
커밋
308be14b4f
@ -444,7 +444,7 @@ export function FleetClusterLayer({ ships, analysisMap: analysisMapProp, onShipS
|
|||||||
const lon = realShip?.lng ?? m.lon;
|
const lon = realShip?.lng ?? m.lon;
|
||||||
features.push({
|
features.push({
|
||||||
type: 'Feature',
|
type: 'Feature',
|
||||||
properties: { mmsi: m.mmsi, name: m.name, groupKey, groupType, role: m.role, isParent: m.isParent ? 1 : 0, color, cog: heading, baseSize: m.isParent ? 0.18 : 0.14 },
|
properties: { mmsi: m.mmsi, name: m.name, groupKey, groupType, role: m.role, isParent: m.isParent ? 1 : 0, isGear: (groupType !== 'FLEET' && !m.isParent) ? 1 : 0, color, cog: heading, baseSize: (groupType !== 'FLEET' && !m.isParent) ? 0.11 : m.isParent ? 0.18 : 0.14 },
|
||||||
geometry: { type: 'Point', coordinates: [lon, lat] },
|
geometry: { type: 'Point', coordinates: [lon, lat] },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -542,7 +542,7 @@ export function FleetClusterLayer({ ships, analysisMap: analysisMapProp, onShipS
|
|||||||
type: 'FeatureCollection',
|
type: 'FeatureCollection',
|
||||||
features: snap.members.map(m => ({
|
features: snap.members.map(m => ({
|
||||||
type: 'Feature' as const,
|
type: 'Feature' as const,
|
||||||
properties: { mmsi: m.mmsi, name: m.name, cog: m.cog ?? 0, role: m.role, stale: isStale ? 1 : 0 },
|
properties: { mmsi: m.mmsi, name: m.name, cog: m.cog ?? 0, role: m.role, isGear: m.role === 'GEAR' ? 1 : 0, stale: isStale ? 1 : 0 },
|
||||||
geometry: { type: 'Point' as const, coordinates: [m.lon, m.lat] },
|
geometry: { type: 'Point' as const, coordinates: [m.lon, m.lat] },
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
@ -748,7 +748,7 @@ export function FleetClusterLayer({ ships, analysisMap: analysisMapProp, onShipS
|
|||||||
id="group-member-icon"
|
id="group-member-icon"
|
||||||
type="symbol"
|
type="symbol"
|
||||||
layout={{
|
layout={{
|
||||||
'icon-image': 'ship-triangle',
|
'icon-image': ['case', ['==', ['get', 'isGear'], 1], 'gear-diamond', 'ship-triangle'],
|
||||||
'icon-size': ['interpolate', ['linear'], ['zoom'],
|
'icon-size': ['interpolate', ['linear'], ['zoom'],
|
||||||
4, ['*', ['get', 'baseSize'], 0.9],
|
4, ['*', ['get', 'baseSize'], 0.9],
|
||||||
6, ['*', ['get', 'baseSize'], 1.2],
|
6, ['*', ['get', 'baseSize'], 1.2],
|
||||||
@ -758,7 +758,7 @@ export function FleetClusterLayer({ ships, analysisMap: analysisMapProp, onShipS
|
|||||||
13, ['*', ['get', 'baseSize'], 4.0],
|
13, ['*', ['get', 'baseSize'], 4.0],
|
||||||
14, ['*', ['get', 'baseSize'], 4.8],
|
14, ['*', ['get', 'baseSize'], 4.8],
|
||||||
],
|
],
|
||||||
'icon-rotate': ['get', 'cog'],
|
'icon-rotate': ['case', ['==', ['get', 'isGear'], 1], 0, ['get', 'cog']],
|
||||||
'icon-rotation-alignment': 'map',
|
'icon-rotation-alignment': 'map',
|
||||||
'icon-allow-overlap': true,
|
'icon-allow-overlap': true,
|
||||||
'icon-ignore-placement': true,
|
'icon-ignore-placement': true,
|
||||||
@ -927,13 +927,17 @@ export function FleetClusterLayer({ ships, analysisMap: analysisMapProp, onShipS
|
|||||||
{historyData && (
|
{historyData && (
|
||||||
<Source id="history-anim-members" type="geojson" data={animMembersGeoJson}>
|
<Source id="history-anim-members" type="geojson" data={animMembersGeoJson}>
|
||||||
<Layer id="history-anim-members-icon" type="symbol" layout={{
|
<Layer id="history-anim-members-icon" type="symbol" layout={{
|
||||||
'icon-image': 'ship-triangle',
|
'icon-image': ['case', ['==', ['get', 'isGear'], 1], 'gear-diamond', 'ship-triangle'],
|
||||||
'icon-size': 0.7,
|
'icon-size': ['case', ['==', ['get', 'isGear'], 1], 0.55, 0.7],
|
||||||
'icon-rotate': ['get', 'cog'],
|
'icon-rotate': ['case', ['==', ['get', 'isGear'], 1], 0, ['get', 'cog']],
|
||||||
'icon-rotation-alignment': 'map',
|
'icon-rotation-alignment': 'map',
|
||||||
'icon-allow-overlap': true,
|
'icon-allow-overlap': true,
|
||||||
}} paint={{
|
}} paint={{
|
||||||
'icon-color': ['case', ['==', ['get', 'stale'], 1], '#64748b', '#a8b8c8'],
|
'icon-color': ['case',
|
||||||
|
['==', ['get', 'stale'], 1], '#64748b',
|
||||||
|
['==', ['get', 'isGear'], 0], '#fbbf24',
|
||||||
|
'#a8b8c8',
|
||||||
|
],
|
||||||
'icon-opacity': ['case', ['==', ['get', 'stale'], 1], 0.4, 0.9],
|
'icon-opacity': ['case', ['==', ['get', 'stale'], 1], 0.4, 0.9],
|
||||||
}} />
|
}} />
|
||||||
<Layer id="history-anim-members-label" type="symbol" layout={{
|
<Layer id="history-anim-members-label" type="symbol" layout={{
|
||||||
@ -942,7 +946,7 @@ export function FleetClusterLayer({ ships, analysisMap: analysisMapProp, onShipS
|
|||||||
'text-offset': [0, 1.5],
|
'text-offset': [0, 1.5],
|
||||||
'text-allow-overlap': false,
|
'text-allow-overlap': false,
|
||||||
}} paint={{
|
}} paint={{
|
||||||
'text-color': '#e2e8f0',
|
'text-color': ['case', ['==', ['get', 'isGear'], 0], '#fbbf24', '#e2e8f0'],
|
||||||
'text-halo-color': 'rgba(0,0,0,0.8)',
|
'text-halo-color': 'rgba(0,0,0,0.8)',
|
||||||
'text-halo-width': 1,
|
'text-halo-width': 1,
|
||||||
}} />
|
}} />
|
||||||
|
|||||||
@ -582,9 +582,10 @@ export function KoreaMap({ ships, allShips, aircraft, satellites, layers, osintF
|
|||||||
if (!dto) continue;
|
if (!dto) continue;
|
||||||
const level = dto.algorithms.riskScore.level;
|
const level = dto.algorithms.riskScore.level;
|
||||||
const color = level === 'CRITICAL' ? '#ef4444' : level === 'HIGH' ? '#f97316' : level === 'MEDIUM' ? '#eab308' : '#22c55e';
|
const color = level === 'CRITICAL' ? '#ef4444' : level === 'HIGH' ? '#f97316' : level === 'MEDIUM' ? '#eab308' : '#22c55e';
|
||||||
|
const isGear = /^.+?_\d+_\d+_?$/.test(s.name || '') ? 1 : 0;
|
||||||
features.push({
|
features.push({
|
||||||
type: 'Feature',
|
type: 'Feature',
|
||||||
properties: { mmsi: s.mmsi, name: s.name || s.mmsi, cog: s.heading ?? 0, color, baseSize: 0.16 },
|
properties: { mmsi: s.mmsi, name: s.name || s.mmsi, cog: s.heading ?? 0, color, baseSize: 0.16, isGear },
|
||||||
geometry: { type: 'Point', coordinates: [s.lng, s.lat] },
|
geometry: { type: 'Point', coordinates: [s.lng, s.lat] },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -769,7 +770,7 @@ export function KoreaMap({ ships, allShips, aircraft, satellites, layers, osintF
|
|||||||
id="analysis-ship-icon"
|
id="analysis-ship-icon"
|
||||||
type="symbol"
|
type="symbol"
|
||||||
layout={{
|
layout={{
|
||||||
'icon-image': 'ship-triangle',
|
'icon-image': ['case', ['==', ['get', 'isGear'], 1], 'gear-diamond', 'ship-triangle'],
|
||||||
'icon-size': ['interpolate', ['linear'], ['zoom'],
|
'icon-size': ['interpolate', ['linear'], ['zoom'],
|
||||||
4, ['*', ['get', 'baseSize'], 1.0],
|
4, ['*', ['get', 'baseSize'], 1.0],
|
||||||
6, ['*', ['get', 'baseSize'], 1.3],
|
6, ['*', ['get', 'baseSize'], 1.3],
|
||||||
@ -779,7 +780,7 @@ export function KoreaMap({ ships, allShips, aircraft, satellites, layers, osintF
|
|||||||
13, ['*', ['get', 'baseSize'], 4.2],
|
13, ['*', ['get', 'baseSize'], 4.2],
|
||||||
14, ['*', ['get', 'baseSize'], 5.0],
|
14, ['*', ['get', 'baseSize'], 5.0],
|
||||||
],
|
],
|
||||||
'icon-rotate': ['get', 'cog'],
|
'icon-rotate': ['case', ['==', ['get', 'isGear'], 1], 0, ['get', 'cog']],
|
||||||
'icon-rotation-alignment': 'map',
|
'icon-rotation-alignment': 'map',
|
||||||
'icon-allow-overlap': true,
|
'icon-allow-overlap': true,
|
||||||
'icon-ignore-placement': true,
|
'icon-ignore-placement': true,
|
||||||
|
|||||||
@ -270,6 +270,24 @@ function ensureTriangleImage(map: maplibregl.Map) {
|
|||||||
ctx.fill();
|
ctx.fill();
|
||||||
const imgData = ctx.getImageData(0, 0, s, s);
|
const imgData = ctx.getImageData(0, 0, s, s);
|
||||||
map.addImage('ship-triangle', { width: s, height: s, data: new Uint8Array(imgData.data.buffer) }, { sdf: true });
|
map.addImage('ship-triangle', { width: s, height: s, data: new Uint8Array(imgData.data.buffer) }, { sdf: true });
|
||||||
|
|
||||||
|
// 어구/어망 마름모 아이콘
|
||||||
|
if (!map.hasImage('gear-diamond')) {
|
||||||
|
const dc = document.createElement('canvas');
|
||||||
|
dc.width = s;
|
||||||
|
dc.height = s;
|
||||||
|
const dx = dc.getContext('2d')!;
|
||||||
|
dx.beginPath();
|
||||||
|
dx.moveTo(s / 2, 4); // top
|
||||||
|
dx.lineTo(s - 4, s / 2); // right
|
||||||
|
dx.lineTo(s / 2, s - 4); // bottom
|
||||||
|
dx.lineTo(4, s / 2); // left
|
||||||
|
dx.closePath();
|
||||||
|
dx.fillStyle = '#ffffff';
|
||||||
|
dx.fill();
|
||||||
|
const dd = dx.getImageData(0, 0, s, s);
|
||||||
|
map.addImage('gear-diamond', { width: s, height: s, data: new Uint8Array(dd.data.buffer) }, { sdf: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Main layer (WebGL symbol rendering — triangles) ──
|
// ── Main layer (WebGL symbol rendering — triangles) ──
|
||||||
@ -311,10 +329,11 @@ export function ShipLayer({ ships, militaryOnly, koreanOnly, hoveredMmsi, focusM
|
|||||||
mmsi: ship.mmsi,
|
mmsi: ship.mmsi,
|
||||||
name: ship.name,
|
name: ship.name,
|
||||||
color: getShipHex(ship),
|
color: getShipHex(ship),
|
||||||
size: SIZE_MAP[ship.category] ?? 0.12,
|
size: (/^.+?_\d+_\d+_?$/.test(ship.name || '') ? 0.8 : 1) * (SIZE_MAP[ship.category] ?? 0.12),
|
||||||
isMil: isMilitary(ship.category) ? 1 : 0,
|
isMil: isMilitary(ship.category) ? 1 : 0,
|
||||||
isKorean: ship.flag === 'KR' ? 1 : 0,
|
isKorean: ship.flag === 'KR' ? 1 : 0,
|
||||||
isCheonghae: ship.mmsi === '440001981' ? 1 : 0,
|
isCheonghae: ship.mmsi === '440001981' ? 1 : 0,
|
||||||
|
isGear: /^.+?_\d+_\d+_?$/.test(ship.name || '') ? 1 : 0,
|
||||||
heading: ship.heading,
|
heading: ship.heading,
|
||||||
mtCategory: getMarineTrafficCategory(ship.typecode, ship.category),
|
mtCategory: getMarineTrafficCategory(ship.typecode, ship.category),
|
||||||
natGroup: getNationalityGroup(ship.flag),
|
natGroup: getNationalityGroup(ship.flag),
|
||||||
@ -507,7 +526,7 @@ export function ShipLayer({ ships, militaryOnly, koreanOnly, hoveredMmsi, focusM
|
|||||||
type="symbol"
|
type="symbol"
|
||||||
filter={shipVisibilityFilter}
|
filter={shipVisibilityFilter}
|
||||||
layout={{
|
layout={{
|
||||||
'icon-image': 'ship-triangle',
|
'icon-image': ['case', ['==', ['get', 'isGear'], 1], 'gear-diamond', 'ship-triangle'],
|
||||||
'icon-size': ['interpolate', ['linear'], ['zoom'],
|
'icon-size': ['interpolate', ['linear'], ['zoom'],
|
||||||
4, ['*', ['get', 'size'], 0.8],
|
4, ['*', ['get', 'size'], 0.8],
|
||||||
6, ['*', ['get', 'size'], 1.0],
|
6, ['*', ['get', 'size'], 1.0],
|
||||||
@ -517,7 +536,7 @@ export function ShipLayer({ ships, militaryOnly, koreanOnly, hoveredMmsi, focusM
|
|||||||
13, ['*', ['get', 'size'], 3.5],
|
13, ['*', ['get', 'size'], 3.5],
|
||||||
14, ['*', ['get', 'size'], 4.2],
|
14, ['*', ['get', 'size'], 4.2],
|
||||||
],
|
],
|
||||||
'icon-rotate': ['get', 'heading'],
|
'icon-rotate': ['case', ['==', ['get', 'isGear'], 1], 0, ['get', 'heading']],
|
||||||
'icon-rotation-alignment': 'map',
|
'icon-rotation-alignment': 'map',
|
||||||
'icon-allow-overlap': true,
|
'icon-allow-overlap': true,
|
||||||
'icon-ignore-placement': true,
|
'icon-ignore-placement': true,
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user