@@ -82,6 +82,8 @@ const renderMarkers = (markers: MarkerOption[]) => {
8282 const kakao = (window as any ).kakao
8383 clearOverlays ()
8484
85+
86+
8587 if (! markers || markers .length === 0 ) return
8688
8789 const bounds = new kakao .maps .LatLngBounds ()
@@ -99,26 +101,23 @@ const renderMarkers = (markers: MarkerOption[]) => {
99101 const pos = new kakao .maps .LatLng (m .lat , m .lng )
100102 const isSelected = props .selectedMarkerId && String (m .id ) === String (props .selectedMarkerId )
101103
102- if (m .type === ' plan' ) {
103- const content = document .createElement (' div' )
104- const bgColor = m .color ? ' ' : ' bg-[#9BCCC4]'
105- content .className = `
106- flex items-center justify-center w-8 h-8 rounded-full font-black text-black border-[2px] border-[#2C2C2C] text-xs
107- transition-all duration-200 ease-in-out
108- ${isSelected ? ' bg-[#FF8A00] scale-125' : bgColor }
109- `
110- if (m .color && ! isSelected ) {
111- content .style .backgroundColor = m .color ;
112- }
113- content .innerHTML = String (m .order || ' ' )
114-
115- const customOverlay = new kakao .maps .CustomOverlay ({
116- position: pos ,
117- content: content ,
118- map: rawMap ,
119- yAnchor: 0.8 , // Adjust yAnchor for smaller size
120- zIndex: isSelected ? 999 : 50 ,
121- })
104+ if (m .type === ' plan' ) {
105+ const content = document .createElement (' div' )
106+ content .className = `
107+ flex items-center justify-center w-12 h-12 rounded-full font-black text-black border-[2px] border-[#2C2C2C]
108+ transition-all duration-200 ease-in-out
109+ ${isSelected ? ' bg-[#FF8A00] scale-110' : ' bg-[#9BCCC4]' }
110+ `
111+ content .innerHTML = String (m .order || ' ' )
112+
113+ const customOverlay = new kakao .maps .CustomOverlay ({
114+ position: pos ,
115+ content: content ,
116+ map: rawMap ,
117+ yAnchor: 0.5 ,
118+ zIndex: isSelected ? 999 : 100 , // Increased zIndex for plan markers
119+ })
120+
122121 content .onclick = () => {
123122 if (m .id !== undefined && m .id !== null ) {
124123 emits (' marker-click' , m .id )
@@ -128,14 +127,13 @@ const renderMarkers = (markers: MarkerOption[]) => {
128127 customOverlay .customId = m .id
129128 customOverlay .customType = m .type
130129 customOverlay .customOrder = m .order
131- customOverlay .customColor = m .color
132130 kakaoOverlays .push (customOverlay )
133131 } else {
134132 const marker = new kakao .maps .Marker ({
135133 position: pos ,
136134 map: rawMap ,
137135 image: isSelected ? selectedImage : normalImage ,
138- zIndex: isSelected ? 999 : 1 ,
136+ zIndex: isSelected ? 999 : 1 , // Search markers stay low
139137 clickable: true ,
140138 })
141139
@@ -152,11 +150,99 @@ const renderMarkers = (markers: MarkerOption[]) => {
152150 bounds .extend (pos )
153151 })
154152
153+ // Draw polyline for plan markers
154+ // Draw polyline for plan markers
155+ renderPlanPolylineAsOverlay (markers )
156+
155157 if (! props .selectedMarkerId ) {
156158 rawMap .setBounds (bounds )
157159 }
158160}
159161
162+ let kakaoPlanPolylineOverlay: any = null
163+
164+ const renderPlanPolylineAsOverlay = (markers : MarkerOption []) => {
165+ const rawMap = toRaw (map .value )
166+ if (! rawMap ) return
167+ const kakao = (window as any ).kakao
168+
169+ // Clear existing overlay
170+ if (kakaoPlanPolylineOverlay ) {
171+ kakaoPlanPolylineOverlay .setMap (null )
172+ kakaoPlanPolylineOverlay = null
173+ }
174+
175+ const linePath = markers
176+ .filter ((m ) => m .type === ' plan' )
177+ .sort ((a , b ) => (a .order || 0 ) - (b .order || 0 ))
178+ .map ((m ) => ({ lat: m .lat , lng: m .lng }))
179+
180+ if (linePath .length < 2 ) return
181+
182+ // SVG로 라인을 그릴 컨테이너 생성
183+ const svgNS = " http://www.w3.org/2000/svg"
184+ const svg = document .createElementNS (svgNS , " svg" )
185+
186+ // Use a large canvas approach to ensure visibility and avoid clipping issues
187+ // Centered on the anchor point.
188+ const CANVAS_SIZE = 4000
189+ const OFFSET = CANVAS_SIZE / 2
190+
191+ svg .style .position = ' absolute'
192+ svg .style .pointerEvents = ' none'
193+ svg .style .width = ` ${CANVAS_SIZE }px `
194+ svg .style .height = ` ${CANVAS_SIZE }px `
195+ svg .style .overflow = ' visible'
196+
197+ // Center the SVG on the anchor point (which will be at top-left 0,0 locally)
198+ // so we shift it back by OFFSET
199+ svg .style .transform = ` translate(-${OFFSET }px, -${OFFSET }px) `
200+
201+ // 지도 투영 변환을 사용하여 좌표 변환
202+ const projection = rawMap .getProjection ()
203+
204+ const startPos = projection .pointFromCoords (new kakao .maps .LatLng (linePath [0 ].lat , linePath [0 ].lng ))
205+
206+ linePath .forEach ((point , i ) => {
207+ if (i === 0 ) return
208+
209+ const prevPoint = linePath [i - 1 ]
210+ const fromPos = projection .pointFromCoords (new kakao .maps .LatLng (prevPoint .lat , prevPoint .lng ))
211+ const toPos = projection .pointFromCoords (new kakao .maps .LatLng (point .lat , point .lng ))
212+
213+ const line = document .createElementNS (svgNS , " line" )
214+
215+ // Coordinates relative to anchor, plus OFFSET to center in SVG
216+ const x1 = (fromPos .x - startPos .x ) + OFFSET
217+ const y1 = (fromPos .y - startPos .y ) + OFFSET
218+ const x2 = (toPos .x - startPos .x ) + OFFSET
219+ const y2 = (toPos .y - startPos .y ) + OFFSET
220+
221+ line .setAttribute (" x1" , String (x1 ))
222+ line .setAttribute (" y1" , String (y1 ))
223+ line .setAttribute (" x2" , String (x2 ))
224+ line .setAttribute (" y2" , String (y2 ))
225+ line .setAttribute (" stroke" , " #000000" )
226+ line .setAttribute (" stroke-width" , " 3" )
227+ line .setAttribute (" stroke-opacity" , " 0.9" )
228+ line .setAttribute (" stroke-linecap" , " round" ) // Smooth joints
229+
230+ svg .appendChild (line )
231+ })
232+
233+ // CustomOverlay로 추가
234+ kakaoPlanPolylineOverlay = new kakao .maps .CustomOverlay ({
235+ position: new kakao .maps .LatLng (linePath [0 ].lat , linePath [0 ].lng ),
236+ content: svg ,
237+ map: rawMap ,
238+ zIndex: 50 , // Between Search(10) and Plan(100)
239+ xAnchor: 0 ,
240+ yAnchor: 0
241+ })
242+
243+ // kakaoOverlays.push(kakaoPlanPolylineOverlay) - Do not push to avoid array growth on zoom re-renders
244+ }
245+
160246const renderPolylines = (polylines : PolylineOption []) => {
161247 const rawMap = toRaw (map .value )
162248 if (! rawMap ) return
@@ -192,10 +278,8 @@ const panTo = (lat: number, lng: number) => {
192278watch (
193279 () => props .selectedMarkerId ,
194280 (newId ) => {
195- if (! mapLoaded .value ) return // Add guard clause
196-
197281 const kakao = (window as any ).kakao
198- if (! kakao || ! kakao . maps ) return
282+ if (! kakao ) return
199283
200284 const normalImage = new kakao .maps .MarkerImage (
201285 DEFAULT_MARKER_SRC ,
@@ -213,17 +297,12 @@ watch(
213297 const content = overlay .getContent ()
214298 if (isTarget ) {
215299 content .classList .remove (' bg-[#9BCCC4]' )
216- content .style .backgroundColor = ' ' ;
217300 content .classList .add (' bg-[#FF8A00]' , ' scale-110' )
218301 } else {
219302 content .classList .remove (' bg-[#FF8A00]' , ' scale-110' )
220- if (overlay .customColor ) {
221- content .style .backgroundColor = overlay .customColor ;
222- } else {
223- content .classList .add (' bg-[#9BCCC4]' )
224- }
303+ content .classList .add (' bg-[#9BCCC4]' )
225304 }
226- overlay .setZIndex (isTarget ? 999 : 50 )
305+ overlay .setZIndex (isTarget ? 999 : 100 )
227306 } else {
228307 // This is a kakao.maps.Marker
229308 overlay .setImage (isTarget ? selectedImage : normalImage )
@@ -251,6 +330,10 @@ const initMap = () => {
251330 })
252331 kakao .maps .event .addListener (mapInstance , ' zoom_changed' , () => {
253332 emits (' map-move' )
333+ // Re-render polyline overlay to adjust line paths for new zoom level
334+ if (props .markers && props .markers .length > 0 ) {
335+ renderPlanPolylineAsOverlay (props .markers )
336+ }
254337 })
255338
256339 map .value = markRaw (mapInstance )
@@ -304,6 +387,9 @@ watch(() => props.level, (newLevel) => {
304387onUnmounted (() => {
305388 clearOverlays ()
306389 clearPolylines ()
390+ if (kakaoPlanPolylineOverlay ) {
391+ kakaoPlanPolylineOverlay .setMap (null )
392+ }
307393 map .value = null
308394})
309395
0 commit comments