diff --git a/README.md b/README.md index 0dc5703b..97f4655d 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,136 @@
- image -

🧭 선따라 길따라 🧭

-

쀑μž₯년측을 μœ„ν•œ 접근성을 λ°”νƒ•μœΌλ‘œ ν•œ μœ„μΉ˜ 기반 μ„œλΉ„μŠ€

-
+

πŸ•ΆοΈ νŒ€ μ†Œκ°œ πŸ•ΆοΈ

+ +image -
+### μ•ˆλ…•ν•˜μ„Έμš”, νŒ€ "따라따라" μž…λ‹ˆλ‹€! +
- -## πŸ‘©β€πŸ’» νŒ€ μ†Œκ°œ - -
(따라핑)
+μ €ν¬λŠ” 6μ£Όκ°„ βš’ **"기술적 도전"** βš’μ„ λͺ©ν‘œλ‘œ μ‚Όμ•˜μŠ΅λ‹ˆλ‹€. -### μ•ˆλ…•ν•˜μ„Έμš”, νŒ€ "따라따라" μž…λ‹ˆλ‹€! +
+ +μ €ν¬λŠ” λ‹¨μˆœνžˆ apiλ‚˜ 라이브러리λ₯Ό **"κ°€μ Έλ‹€ μ¨μ„œ" κ΅¬ν˜„ ν•˜λŠ” 것이 μ•„λ‹Œ**, + +**λͺ¨λ“  것을 "직접 κ΅¬ν˜„"** 해보고, κ·Έ κ³Όμ •μ—μ„œ 깊이 μžˆλŠ” 기술적인 도전을 ν•΄ λ‚˜κ°‘λ‹ˆλ‹€. + + +



+ +
+ image +

-## πŸ— 링크 μ•ˆλ‚΄ - -

- νŒ€ μœ„ν‚€ | - νŒ€ λ…Έμ…˜ | - κΈ°νšμ„œ | - λ””μžμΈ - -

+image + +(λͺ¨λ°”μΌμ—μ„œ μœ„ QR μ½”λ“œλ‘œ μ ‘μ†ν•˜μ‹€ 수 μžˆμŠ΅λ‹ˆλ‹€.) + +

+
+image +image +image
+
+ +
+image +image +image +
+ + + + +
+ +



+ +![image](https://github.com/user-attachments/assets/7251fa39-1c7c-4005-9187-a2b560c2405f) + + + + + + +

+ +## πŸ’‘ 핡심 κΈ°λŠ₯ + +### 1. 지도 μœ„ μΊ”λ²„μŠ€μ— μΆœλ°œμ§€/도착지 마컀, 경둜 그리기 + +- μΊ”λ²„μŠ€ μœ„μ— κ·Έλ €λ‘” 그림은 지도(μΊ”λ²„μŠ€)λ₯Ό μ΄λ™ν•˜κ±°λ‚˜ ν™•λŒ€/μΆ•μ†Œν•˜λ”λΌλ„ κ·Έλ¦° μœ„μΉ˜(지도 κΈ°μ€€)에 κ·ΈλŒ€λ‘œ μ‘΄μž¬ν•΄μ•Ό 함 +
-


+image +image +
+ +

+ +### 2. μ‹€μ‹œκ°„ μœ„μΉ˜ νŒŒμ•… + +- 호슀트(μ†μž)λŠ” κ²ŒμŠ€νŠΈλ“€(ν• λ¨Έλ‹ˆ, 할아버지)의 μœ„μΉ˜ μ‹€μ‹œκ°„μœΌλ‘œ 확인 κ°€λŠ₯ν•΄μ•Ό 함 +- 게슀트(ν• λ¨Έλ‹ˆ)λŠ” 본인의 μœ„μΉ˜μ™€ 호슀트(μ†μž)κ°€ μ„€μ •ν•΄λ‘” μΆœλ°œμ§€, 도착지, κ²½λ‘œλ§Œμ„ 확인할 수 μžˆμ–΄μ•Ό 함 + + +
+image +
+ +

+ +## πŸ“ƒ 우리의 기술적 도전 + +### by. J060_김주원 + +- [[FE] 🎨 μ§€λ„μ˜ κΈ°λŠ₯λ“€ canvasμ—μ„œ κ΅¬ν˜„ν•˜κΈ°](https://velog.io/@jackson5272/%EC%A7%80%EB%8F%84%EC%9D%98-%EA%B8%B0%EB%8A%A5%EB%93%A4-canvas%EC%97%90%EC%84%9C-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0) + +### by. J174_μ΄λ™μœ¨ + +- [[FE] πŸ’Ύ 초보 개발자의 데이터 관리 μ‹œν–‰μ°©μ˜€](https://misty-thread-f6e.notion.site/FE-14f87662ce1380b39c90d38e24a2dad8?pvs=4) +### by. J210_μž„μž¬λ„ + +- [[FE] 🧬 μΊ”λ²„μŠ€μ™€ 넀이버 지도 APIλ₯Ό μ—°λ™ν•˜κΈ° μœ„ν•œ κ³Όμ •](https://fantasmith.com/dev-lab/challenge/boostcamp/ddara/canvas-and-map-linking) + + +### by. J234_μ •ν˜œμΈ + +- [[FE] πŸ—ΊοΈ 지도와 ν•¨κ»˜ μ›€μ§μ΄λŠ” μΊ”λ²„μŠ€ 섀계/κ΅¬ν˜„ μ‹œν–‰μ°©μ˜€ μŠ€ν† λ¦¬](https://velog.io/@happyhyep/%EC%A7%80%EB%8F%84%EC%99%80-%ED%95%A8%EA%BB%98-%EC%9B%80%EC%A7%81%EC%9D%B4%EB%8A%94-%EC%BA%94%EB%B2%84%EC%8A%A4-%EA%B5%AC%ED%98%84-%EC%8A%A4%ED%86%A0%EB%A6%AC) + +- [[BE] πŸ”Œ μ‹€μ‹œκ°„ μ†ŒμΌ“ ν†΅μ‹ μ˜ 도전기: λ‹€μ–‘ν•œ 쑰건이 μ‘΄μž¬ν•˜λŠ” 톡신 feat. ν΄λΌμ΄μ–ΈνŠΈμ™€ μ„œλ²„ κ°κ°μ—μ„œμ˜ κ³ λ―Όκ³Ό 섀계 κ³Όμ •](https://velog.io/@happyhyep/%EC%8B%A4%EC%8B%9C%EA%B0%84-%EC%86%8C%EC%BC%93-%ED%86%B5%EC%8B%A0%EC%9D%98-%EB%8F%84%EC%A0%84%EA%B8%B0-%EB%8B%A4%EC%96%91%ED%95%9C-%EC%A1%B0%EA%B1%B4%EC%9D%B4-%EC%A1%B4%EC%9E%AC%ED%95%98%EB%8A%94-%ED%86%B5%EC%8B%A0) + + + +

+ +## ⛏ μ„œλΉ„μŠ€ μ•„ν‚€ν…μ²˜ + +image + + + + +

## 🌱 νŒ€μ› μ†Œκ°œ |J060_김주원|J174_μ΄λ™μœ¨|J210_μž„μž¬λ„|J234_μ •ν˜œμΈ| |:--:|:--:|:--:|:--:| -||||| +||||| |FE|FE|FE|Full Stack (FE + BE)| |@juwon5272|@leedongyull|@effozen|@happyhyep| -
- -## ⛏ 기술 μŠ€νƒ -μΆ”ν›„ μΆ”κ°€ μ˜ˆμ •μž…λ‹ˆλ‹€. +

- - diff --git a/frontend/src/assets/endmarker.svg b/frontend/src/assets/endmarker.svg index c09cae89..beab2b0a 100644 --- a/frontend/src/assets/endmarker.svg +++ b/frontend/src/assets/endmarker.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/frontend/src/assets/mylocation.svg b/frontend/src/assets/mylocation.svg index 37fadb2b..b2b0ad7d 100644 --- a/frontend/src/assets/mylocation.svg +++ b/frontend/src/assets/mylocation.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/frontend/src/assets/startmarker.svg b/frontend/src/assets/startmarker.svg index 4c01f3ca..31934eb8 100644 --- a/frontend/src/assets/startmarker.svg +++ b/frontend/src/assets/startmarker.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/frontend/src/component/IconGuide/GuestMarker.tsx b/frontend/src/component/IconGuide/GuestMarker.tsx index 44d00b7d..9aef156a 100644 --- a/frontend/src/component/IconGuide/GuestMarker.tsx +++ b/frontend/src/component/IconGuide/GuestMarker.tsx @@ -1,6 +1,8 @@ -import { MdAssistantNavigation, MdFlag, MdLocationOn } from 'react-icons/md'; +import { MdFlag, MdLocationOn } from 'react-icons/md'; import { IconContext } from 'react-icons'; import { ReactNode, useMemo } from 'react'; +import { START_MARKER_COLOR, END_MARKER_COLOR, PATH_COLOR } from '@/lib/constants/canvasConstants'; +import { IoFootsteps } from 'react-icons/io5'; interface IMarkerData { name: string; @@ -11,14 +13,14 @@ interface IGuestMarkerProps { markerColor: string; } -export const GusetMarker = (props: IGuestMarkerProps) => { +export const GuestMarker = (props: IGuestMarkerProps) => { const markerData: IMarkerData[] = [ { - name: 'λ‚΄ μœ„μΉ˜', - icon: , + name: '경둜', + icon: , }, - { name: '도착지', icon: }, - { name: 'μΆœλ°œμ§€', icon: }, + { name: '도착지', icon: }, + { name: 'μΆœλ°œμ§€', icon: }, ]; const iconContextValue = useMemo(() => ({ color: 'purple', className: 'size-5' }), []); @@ -28,7 +30,7 @@ export const GusetMarker = (props: IGuestMarkerProps) => {
    {markerData.map(data => ( -
  • +
  • {data.icon} {data.name}
  • diff --git a/frontend/src/hooks/useRedraw.ts b/frontend/src/hooks/useRedraw.ts index 1944b0d2..713874b4 100644 --- a/frontend/src/hooks/useRedraw.ts +++ b/frontend/src/hooks/useRedraw.ts @@ -1,5 +1,11 @@ import { useRef, useEffect, RefObject } from 'react'; -import { LINE_WIDTH, STROKE_STYLE } from '@/lib/constants/canvasConstants.ts'; +import { + END_MARKER_COLOR, + LINE_WIDTH, + PATH_COLOR, + START_MARKER_COLOR, + STROKE_STYLE, +} from '@/lib/constants/canvasConstants.ts'; import startmarker from '@/assets/startmarker.svg'; import endmarker from '@/assets/endmarker.svg'; @@ -88,19 +94,58 @@ export const useRedrawCanvas = ({ footprintRef.current.src = footprint; }, []); + // μΊ”λ²„μŠ€μ—μ„œ 이미지 그리고, μΊ”λ²„μŠ€ 전체 색상 λ³€κ²½ 후에 λ°˜ν™˜ν•˜λŠ” ν•¨μˆ˜ + const colorizeImage = ( + image: HTMLImageElement, + color: string, + width: number, + height: number, + ): HTMLCanvasElement => { + const tempCanvas = document.createElement('canvas'); + tempCanvas.width = width; + tempCanvas.height = height; + + const tempCtx = tempCanvas.getContext('2d'); + if (!tempCtx) return tempCanvas; + + // 원본 이미지 그리기 + tempCtx.drawImage(image, 0, 0, width, height); + + // 색상 적용 + tempCtx.globalCompositeOperation = 'source-in'; + tempCtx.fillStyle = color; + tempCtx.fillRect(0, 0, width, height); + + return tempCanvas; + }; + + const checkMarker = (path: string) => { + if (path.includes('startmarker') || path.includes('endmarker') || path.includes('mylocation')) { + return true; + } + return false; + }; + const drawMarker = ( ctx: CanvasRenderingContext2D, point: { x: number; y: number } | null, image: HTMLImageElement | null, zoom: number, rotate: number, + color: string, ) => { if (point && image) { const markerSize = zoom * 5; + ctx.fillStyle = color || '#000'; + ctx.strokeStyle = color || '#000'; ctx.save(); ctx.translate(point.x, point.y); ctx.rotate(rotate); - ctx.drawImage(image, -markerSize / 2, -markerSize / 2, markerSize, markerSize); + let filteredImage; + if (checkMarker(image.src)) + filteredImage = colorizeImage(image, color, markerSize, markerSize); + else filteredImage = image; + ctx.drawImage(filteredImage, -markerSize / 2, -markerSize / 2, markerSize, markerSize); ctx.restore(); } }; @@ -196,20 +241,13 @@ export const useRedrawCanvas = ({ // } // } // }; - const drawPath = (ctx: CanvasRenderingContext2D, points: ILatLng[]) => { + const drawPath = (ctx: CanvasRenderingContext2D, points: ILatLng[], color: string) => { if (points.length === 0 || !footprintRef.current || !map) return; const footprintImage = footprintRef.current; const markerSize = Math.min(map.getZoom() * 2, 20); - const offscreenCanvas = document.createElement('canvas'); - const offscreenCtx = offscreenCanvas.getContext('2d'); - if (!offscreenCtx) return; - - offscreenCanvas.width = markerSize; - offscreenCanvas.height = markerSize; - - offscreenCtx.drawImage(footprintImage, 0, 0, markerSize, markerSize); + const offscreenCanvas = colorizeImage(footprintImage, color, markerSize, markerSize); for (let i = 0; i < points.length - 1; i++) { const start = latLngToCanvasPoint(points[i]); @@ -237,6 +275,7 @@ export const useRedrawCanvas = ({ ctx.drawImage(offscreenCanvas, -markerSize / 2, -markerSize / 2); ctx.restore(); } + ctx.stroke(); } }; @@ -253,27 +292,42 @@ export const useRedrawCanvas = ({ ctx.lineCap = 'round'; ctx.lineJoin = 'round'; + // ν˜ΈμŠ€νŠΈκ°€ 게슀트 경둜 κ·Έλ¦΄λ•Œ μ“°μ΄λŠ” λ””μžμΈ const zoom = map.getZoom(); if (startMarker) { const startPoint = latLngToCanvasPoint(startMarker); - drawMarker(ctx, startPoint, startImageRef.current, zoom, 0); + drawMarker(ctx, startPoint, startImageRef.current, zoom, 0, START_MARKER_COLOR); } if (endMarker) { const endPoint = latLngToCanvasPoint(endMarker); - drawMarker(ctx, endPoint, endImageRef.current, zoom, 0); + drawMarker(ctx, endPoint, endImageRef.current, zoom, 0, END_MARKER_COLOR); } if (pathPoints) { - drawPath(ctx, pathPoints); + drawPath(ctx, pathPoints, PATH_COLOR); } if (lat && lng) { const currentLocation = latLngToCanvasPoint({ lat, lng }); if (alpha) { - drawMarker(ctx, currentLocation, character1Ref.current, zoom, (alpha * Math.PI) / 180); + drawMarker( + ctx, + currentLocation, + character1Ref.current, + zoom, + (alpha * Math.PI) / 180, + guests![0]?.markerStyle.color, + ); } else { - drawMarker(ctx, currentLocation, character1Ref.current, zoom, 0); + drawMarker( + ctx, + currentLocation, + character1Ref.current, + zoom, + 0, + guests![0]?.markerStyle.color, + ); } } @@ -291,19 +345,20 @@ export const useRedrawCanvas = ({ character2Ref.current, zoom, (location.alpha * Math.PI) / 180, + color, ); }); } if (guests) { - guests.forEach(({ startPoint, endPoint, paths }) => { + guests.forEach(({ startPoint, endPoint, paths, markerStyle }) => { const startLocation = latLngToCanvasPoint(startPoint); - drawMarker(ctx, startLocation, startImageRef.current, zoom, 0); + drawMarker(ctx, startLocation, startImageRef.current, zoom, 0, markerStyle.color); const endLocation = latLngToCanvasPoint(endPoint); - drawMarker(ctx, endLocation, endImageRef.current, zoom, 0); + drawMarker(ctx, endLocation, endImageRef.current, zoom, 0, markerStyle.color); - drawPath(ctx, paths); + drawPath(ctx, paths, markerStyle.color); }); } }; diff --git a/frontend/src/lib/constants/canvasConstants.ts b/frontend/src/lib/constants/canvasConstants.ts index 915079d0..4beb3c3e 100644 --- a/frontend/src/lib/constants/canvasConstants.ts +++ b/frontend/src/lib/constants/canvasConstants.ts @@ -2,6 +2,7 @@ export const LINE_WIDTH = 30; export const STROKE_STYLE = 'black'; export const START_MARKER_COLOR = '#4CAF50'; export const END_MARKER_COLOR = '#F44336'; +export const PATH_COLOR = '#2196F3'; export const NAVER_STEP_SCALES = [ 100, 100, 100, 100, 100, 100, 50, 30, 20, 10, 5, 3, 1, 0.5, 0.3, 0.1, 0.05, 0.03, 0.02, 0.01, 0.005, diff --git a/frontend/src/pages/GuestView.tsx b/frontend/src/pages/GuestView.tsx index 03638a4c..c37ac61f 100644 --- a/frontend/src/pages/GuestView.tsx +++ b/frontend/src/pages/GuestView.tsx @@ -5,7 +5,7 @@ import { useLocation } from 'react-router-dom'; import { MapCanvasForView } from '@/component/canvasWithMap/canvasWithMapForView/MapCanvasForView.tsx'; import { IPoint } from '@/lib/types/canvasInterface.ts'; import { guestEntity } from '@/api/dto/channel.dto.ts'; -import { GusetMarker } from '@/component/IconGuide/GuestMarker.tsx'; +import { GuestMarker } from '@/component/IconGuide/GuestMarker.tsx'; import { LoadingSpinner } from '@/component/common/loadingSpinner/LoadingSpinner.tsx'; import { getUserLocation } from '@/hooks/getUserLocation.ts'; import { loadLocalData, saveLocalData } from '@/utils/common/manageLocalData.ts'; @@ -93,7 +93,7 @@ export const GuestView = () => { return (
    - + {/* eslint-disable-next-line no-nested-ternary */} {lat && lng ? ( guestInfo ? ( diff --git a/frontend/src/pages/HostView.tsx b/frontend/src/pages/HostView.tsx index 3a40aeab..c2bb1b54 100644 --- a/frontend/src/pages/HostView.tsx +++ b/frontend/src/pages/HostView.tsx @@ -20,6 +20,7 @@ interface IOtherLocationsInHostView { token: string; color: string; } + export const HostView = () => { const { lat, lng, alpha, error } = getUserLocation(); const location = useLocation();