feat: P5-2 작은 후속 (#338+#320+#340+#333)
#338: /api/version 신규 - HealthController에 @Value 빌드 정보 + GET /api/version 추가 - SecurityConfig.permitAll에 /api/version 추가 - application.yml app.build.version/commit (env APP_VERSION/APP_COMMIT) - 부수: SecurityConfig에서 /api/daemon/config permitAll 제거 (이미 admin-only) #320: findRegionFromCoords 거리 보정 - 유클리드 거리 → cos(lat) 가중치(equirectangular approx)로 위경도 실거리 보정 - 위도가 큰 지역(부산↔서울)에서 city 추정 정확도 향상 #340: MapView 마커/범례 ARIA - 클러스터 마커: role=button + aria-label - 개별 식당 마커: role=button + aria-label (name + 폐업 여부) - 채널 범례: role=region + aria-label, 색상 점은 aria-hidden #333: ChannelController 캐시 세분화 - cache.flush() 전체 무효화 → cache.del(makeKey("channels"))로 채널 키만 evict - 다른 모듈(restaurants/search) 캐시 hit율 보존 후속: deploy.sh에 APP_VERSION/APP_COMMIT env 주입은 별도 (현재 dev/unknown 응답) Refs: #338 #320 #340 #333
This commit is contained in:
@@ -168,10 +168,15 @@ function findRegionFromCoords(
|
||||
}
|
||||
let best: { country: string; city: string } | null = null;
|
||||
let bestDist = Infinity;
|
||||
// #320 — 유클리드 거리는 적도/극지에서 경도·위도의 실거리 차이가 커서 왜곡됨.
|
||||
// cos(lat) 가중치(equirectangular approximation)로 위도 의존 보정.
|
||||
const cosLat = Math.cos((lat * Math.PI) / 180);
|
||||
for (const g of groups.values()) {
|
||||
const cLat = g.lats.reduce((a, b) => a + b, 0) / g.lats.length;
|
||||
const cLng = g.lngs.reduce((a, b) => a + b, 0) / g.lngs.length;
|
||||
const dist = (cLat - lat) ** 2 + (cLng - lng) ** 2;
|
||||
const dLat = cLat - lat;
|
||||
const dLng = (cLng - lng) * cosLat;
|
||||
const dist = dLat * dLat + dLng * dLng;
|
||||
if (dist < bestDist) {
|
||||
bestDist = dist;
|
||||
best = { country: g.country, city: g.city };
|
||||
|
||||
@@ -209,6 +209,8 @@ function MapContent({ restaurants, selected, onSelectRestaurant, flyTo, activeCh
|
||||
zIndex={100}
|
||||
>
|
||||
<div
|
||||
role="button"
|
||||
aria-label={`${point_count}개 식당이 모인 클러스터, 클릭하면 확대됩니다`}
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
@@ -246,7 +248,10 @@ function MapContent({ restaurants, selected, onSelectRestaurant, flyTo, activeCh
|
||||
onClick={() => handleMarkerClick(r)}
|
||||
zIndex={isSelected ? 1000 : 1}
|
||||
>
|
||||
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", transition: "transform 0.2s ease", transform: isSelected ? "scale(1.15)" : "scale(1)", opacity: isClosed ? 0.5 : 1 }}>
|
||||
<div
|
||||
role="button"
|
||||
aria-label={`${r.name}${isClosed ? ' (폐업)' : ''}, 클릭하면 상세 정보가 표시됩니다`}
|
||||
style={{ display: "flex", flexDirection: "column", alignItems: "center", transition: "transform 0.2s ease", transform: isSelected ? "scale(1.15)" : "scale(1)", opacity: isClosed ? 0.5 : 1 }}>
|
||||
<div
|
||||
style={{
|
||||
padding: "4px 8px",
|
||||
@@ -389,10 +394,15 @@ export default function MapView({ restaurants, selected, onSelectRestaurant, onB
|
||||
</button>
|
||||
)}
|
||||
{channelNames.length > 0 && (
|
||||
<div className="absolute bottom-2 left-2 bg-surface/90 backdrop-blur-sm rounded-lg shadow px-2.5 py-1.5 flex flex-wrap gap-x-3 gap-y-1 text-[11px] z-10">
|
||||
<div
|
||||
role="region"
|
||||
aria-label="채널 범례"
|
||||
className="absolute bottom-2 left-2 bg-surface/90 backdrop-blur-sm rounded-lg shadow px-2.5 py-1.5 flex flex-wrap gap-x-3 gap-y-1 text-[11px] z-10"
|
||||
>
|
||||
{channelNames.map((ch) => (
|
||||
<div key={ch} className="flex items-center gap-1">
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="inline-block w-2.5 h-2.5 rounded-full border"
|
||||
style={{ backgroundColor: channelColors[ch].border, borderColor: channelColors[ch].border }}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user