leaflet, 반복되는 지도에 marker 표시하기(react)

맵 축소 시 반복되는 지도에 Marker 반영하기

다른 옵션을 설정하지 않는 한 맵을 최대로 축소하면 다음과 같이 지도가 반복되어 나타납니다.

여기서 맵을 오른쪽이나 왼쪽으로 넘겨서 중심을 바꿔도 Marker는 그대로 있습니다.

하지만 맵 렌더링 시 다음과 같이 ‘worldCopyJump’ 옵션을 넣어주면 중심이 이동하더라도 기존의 Marker를 모두 표시할 수 있습니다.

<MapContainer
   center={[35.102, 129.067]}
   zoom={5}
   scrollWheelZoom={true}
   worldCopyJump
>

옵션 하나만으로 맵을 이동하면 Marker도 이동하여 같은 좌표에 표시되는 것을 확인할 수 있습니다.

Next.js + Leaflet(OpenStreetMap) 초기 설정하기

Next.js와 leaflet이 만나기 위해서는 참고해야 할 사이트가 많습니다.

leafletjs.com
react-leaflet.js.org
openstreetmap.org

간략하게 정리해보겠습니다.


1. 라이브러리 설치

Next.js + Typescript는 설치되었다고 가정하겠습니다.(관련 포스팅 클릭)

npm i leaflet react-leaflet

Typescript 지원을 위한 라이브러리도 설치합니다.

npm i -D @types/leaflet

2. 코드 구현하기

다음과 같이 컴포넌트를 생성합니다.

import “leaflet/dist/leaflet.css”; 를 설정하지 않으면 맵이 깨져서 표시가 되고 style에 사이즈를 설정하지 않으면 하얀 화면만 나오니 두 부분 모두 주의해야 합니다.

scrollWheelZoom은 스크롤 확대/축소 기능을 설정합니다.

import { MapContainer, TileLayer, useMap, Marker, Popup } from "react-leaflet";
import "leaflet/dist/leaflet.css";

const MyMap = () => {
  return (
    <MapContainer
      center={[37.56675, 126.97842]}
      zoom={10}
      scrollWheelZoom={true}
      style={{ width: "500px", height: "500px" }}
    >
      <TileLayer
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <Marker position={[37.56675, 126.97842]}>
        <Popup>서울 시청</Popup>
      </Marker>
    </MapContainer>
  );
};

export default MyMap;

별도의 컴포넌트를 생성하는 이유는 관리를 위한 분리도 있지만 Next.js의 특성상 서버 렌더링 시 window 전역 객체에 접근할 수 없는 문제로 인해 발생하는 에러를 해결하기 위해서입니다.

DOM이 생성된 뒤 실행되는 useEffect를 사용하거나 레이지 로딩 기능인 dynamic을 사용할 수 있으며 여기서는 dynamic 기능을 사용하겠습니다.

같은 위치에 다음 컴포넌트를 생성합니다.

import dynamic from "next/dynamic";

const MyMap = dynamic(() => import("./MyMap"), { ssr: false });

const ShowMap = () => {
  return <MyMap />;
};

export default ShowMap;

이것으로 기본 구현은 완료되었으며 다음과 같이 맵을 호출하면 됩니다.

<ShowMap />

결과는 다음과 같습니다.

이것으로 Next.js에서 Leaflet을 사용하기 위한 설정이 완료되었지만 Marker 이미지가 깨지는 현상이나 언어 설정 등 추가할 부분이 많습니다.

해당 내용은 다른 포스트에서 다루도록 하겠습니다.

map, reduce, filter 서로 대체가 가능할까 (javascript)

자바스크립트 배열을 처리하는 메서드 map, reduce, filter

자바스크립트에서 배열의 요소에 접근하는 대표 메서드인 map, reduce, filter는 개발자라면 필수적으로 그리고 좀 더 상세히 알고 가면 좋을 것 같습니다.

각각의 특징에 대해 이야기하면 대부분은 아마도

  • map -> 모든 요소에 한 번씩 접근하기
  • reduce -> 모든 요소를 하나로 합치기
  • filter -> 모든 요소에서 원하는 것만 골라내기

정도의 개념을 갖고 있을 것 같습니다.

굳이 위 메서드가 아니라 forEach 문 등 다른 방식을 사용해도 같은 로직의 구현이 가능한데요.

그렇다면 위 메서드끼리도 서로 변경하여 같은 결과를 반환하도록 사용이 가능할까요?

각 메서드의 기능을 살펴보면서 알아보도록 하겠습니다.


1. map

먼저 메서드는 위와 같으며 *가 붙어있는 파라미터는 생략할 수 있습니다.

인덱스는 현재값의 인덱스, 배열은 map이 순회하는 배열 array를 의미합니다.

이제 map을 사용하는 가장 기본적인 코드를 확인해 보겠습니다.

const myArray = [10, 20, 30, 40, 50];

const newArray = myArray.map((value) => { return value });

console.log(newArray);  // [10, 20, 30, 40, 50]
console.log(myArray===newArray); //false

map은 해당 배열 자체를 바꾸는 것이 아니므로 리턴값을 사용하기 위해서는 다른 변수에 넣어 주어야 합니다.

파라미터에서 index는 현재 접근한 요소의 index이며 array는 순회하는 배열 전체를 전달합니다.

const myArray = [10, 20, 30, 40, 50];

const newArray = myArray.map((value, index, array) => {
   if(index%2){
     return value-array[1];   
   }else{
     return value-array[0];
   }                              
});

console.log(newArray);

다음 코드는 자주 접하는 실수 중 하나로 코드의 동작 여부를 생각해 볼 필요가 있습니다.

const myData = {10, 20, 30, 40, 50};

const newData = myData.map((value) => {return value+1});

console.log(newData);

위 코드는 에러가 발생합니다.

바로 map의 특성 때문인데요. map은 배열에서 동작하는 메서드이며 객체 object에서는 동작하지 않습니다.

하지만 다음과 같이 배열 내부의 object는 접근 가능합니다.

const myData = [{name:'chamchi', age:3},{name:'ggongchi', age:2},{name:'myulchi', age:1}];

const newData = myData.map((value) => value.name );

console.log(newData); //['chamchi', 'ggongchi', 'myulchi']

그렇다면 map을 사용해 reduce나 filter 메서드와 같은 로직을 구현할 수 있을까요?

다음 코드를 통해 확인할 수 있을 것 같습니다.

const myArray = [1, 2, 3, 4, 5];

const newArray = myArray.map((value) => {
   if(value%2){
     return value;   
   }                            
});

console.log(newArray); //[1, undefined, 3, undefined, 5]

홀수의 요소만 반환하고자 위와 같이 작성하더라도 map은 모든 요소를 반환합니다.

짝수 부분은 리턴값이 정의되지 않아 undefined로 반환된 것을 확인할 수 있습니다.

따라서 map은 reduce, filter의 기능은 구현할 수 없을 것 같습니다.


2. reduce

먼저 메서드는 위와 같으며 *가 붙어있는 파라미터는 생략할 수 있습니다.

누적값은 배열을 순회하면서 작업의 처리 결과를 누적하는 값으로 순회가 종료되면 최종 리턴값이 됩니다.

reduce의 예시로 모든 요소를 더하는 예시가 많다보니 용도는 전체 요소 더하거나 빼서 하나로 만들기라고

알고 있을 수 있지만 누적값 설정을 통해 다양한 방식으로 활용이 가능합니다.

인덱스는 현재값의 인덱스, 배열은 reduce가 순회하는 array 배열을 의미합니다.

마지막에 있는 시작값은 누적값의 처음 기본값을 전달합니다.

생략하면 0이 기본값이 됩니다.

const myArray = [10, 20, 30, 40, 50];
const newArray = myArray.reduce((acc, value) => { return acc + value });

console.log(newArray); // 150

시작값이 생략되었으므로 acc의 기본값은 0이 됩니다.

그럼 초기값을 설정한 샘플을 보겠습니다.

const myArray = [10, 20, 30, 40, 50];
const newArray = myArray.reduce((acc, value) => { return acc + value },100);

console.log(newArray); // 250

이를 응용하여 초기값을 설정하면 다양한 결과를 만들 수 있습니다.

먼저 배열을 다른 배열로 복사하는 기능을 구현해 보겠습니다.

const myArray = [10, 20, 30, 40, 50];
const newArray = myArray.reduce((acc, value) => { 
     acc.push(value);
     return acc; 
 },[]);

console.log(newArray); // [10, 20, 30, 40, 50]

이것으로 map과 똑같은 로직의 구현이 가능합니다.

그렇다면 filter를 구현해 보겠습니다.

const myArray = [10, 20, 30, 40, 50];
const newArray = myArray.reduce((acc, value, index) => { 
     if(index%2){
       acc.push(value);
     }
     return acc; 
 },[]);

console.log(newArray); // [20, 40]

코드를 통해 확인해보니 reduce는 초기값 설정을 통해 map이나 filter와 같은 로직의 구현이 가능합니다.


3. filter

filter는 말 그대로 배열의 요소를 필터링하여 조건이 참이 되는 요소만 반환합니다.

const myArray = [10, 20, 30, 40, 50];

const newArray = myArray.filter((value) => { 
    return value%20 === 0
 });

console.log(newArray); // [20, 40]

filter는 필터링 기능만 갖고 있어 map이나 reduce의 로직을 만들긴 어려울 것 같습니다.



map과 filter는 용도가 조금 더 특정되어 있다보니 다른 메서드의 로직을 그대로 구현하기는 어렵지만 reduce는 초기값을 통해 다양한 로직의 구현이 가능해 다재다능한 메서드인 것 같습니다.

자바스크립트 배열+반복문의 속도 테스트(map, for, while, for…in, for…of)

반복문의 배열 작업 속도를 확인하기

배열 관련하여 각 요소별로 순회하는 반복문 작업을 할 때 가장 익숙한 방법을 사용하거나 때로는 가장 먼저 떠오르는 방법을 사용하기도 합니다.

배열의 크기가 작으면 상관이 없지만 배열의 크기가 커지면 작은 차이가 큰 차이를 만들어낼 수 있습니다.

문득 연산 속도가 가장 빠른 반복문이 무엇일까라는 궁금증이 생겨 간단하게 하나의 배열 데이터를 다른 배열로 옮기는 연산을 통해 속도를 테스트해보고자 합니다.

크기에 상관없이 반복문 별로 같은 효율을 보여주리라 생각했지만 배열의 크기에 따라 각각 다른 속도를 보여주는 점이 흥미로웠습니다.


1. 테스트 방법

먼저 일정 크기를 갖는 배열 array을 생성합니다.

console.time 메서드를 사용해 이 배열의 데이터를 다른 배열로 복사하는 작업을 진행하고 이 작업 시간을 측정합니다.

테스트는 map, for, while, for…in, for…of의 다섯 가지 반복문을 사용합니다.

// 배열 데이터 생성
const array = [];
for(let i=0;i<10000;i++){
  array.push(i);
}

// 데이터가 복사될 배열
let arraymap = [];

console.time('map');  //타이머 시작
arraymap = array.map(list=>list);
console.timeEnd('map'); //타이머 종료

위와 같은 방식으로 3회씩 테스트를 진행하며 배열의 크기를 조절하여 반복문 별로 상대적인 작업 속도의 변화를 확인합니다.


2. 테스트

먼저 배열의 크기가 아주 작을 때는 모두 큰 차이를 보이지 않습니다.

다음은 배열의 크기가 100일 때 결과입니다.

작업을 세 번 진행한 결과 map과 for…of가 엎치락뒤치락하지만 항상 가장 빠른 속도를 보여줍니다.

for, while, for…in도 세번 모두 결과가 엎치락뒤치락하지만 위의 두 반복문보단 느린 속도를 보여줍니다.

그럼 배열의 크기를 1000으로 올려보겠습니다.

크기가 1000일 때는 대체적으로 비슷한 속도를 보여줍니다.

for…in만 다른 반복문보다 느린 것이 확인됩니다.

이제 확연하게 유의미한 차이를 느낄 수 있는 크기인 10000으로 올려보겠습니다.

10000에서는 map이 가장 우수한 속도를 보여줍니다.

눈에 띄는 부분은 작은 사이즈에서는 map과 대등한 속도를 보이던 for…of가 여기서는 가장 느린 속도를 기록합니다.

또한 for…in은 오히려 for…of보다 빠른 속도를 보여줍니다.

그럼 1000000으로 확인해 보겠습니다.

배열이 매우 커지니 작업 속도는 for문이 가장 준수합니다.

map, while도 큰 차이를 보이지 않고 준수한 성능을 보여줍니다.

for…of는 확연하게 조금 느린 속도를 보여주고 for…in은 이제 인사를 해야할 것 같습니다.

그럼 마지막으로 10000000(천만)으로 테스트를 해보겠습니다.

세 번 테스트한 결과 모두 for 문이 가장 빠른 속도를 보여주었으며, map, while도 의미있는 속도를 보여줍니다.

확실히 for…of는 위의 세 반복문보다 느린 속도를 보여주었으며 for…in은 10배가 넘는 느린 속도의 차이를 보여줍니다.


3. 결과 정리

배열 테이터의 크기가 작을 때는 의미있는 차이가 없지만 배열이 커질수록 map, for, while의 속도가 우수하고 for…in, for…of는 속도 저하를 보이는 결과를 나타냅니다.

어디까지나 단순한 작업인 배열 데이터 복사 기능만을 사용한 테스트이므로 작업 내용에 따라 다른 결과를 보일 가능성이 있어 단순 참고용으로 삼으면 좋을 것 같습니다.

다음은 테스트에 사용한 코드 전체입니다.

console.log('-------------start----------------')

const array = [];

for(let i=0;i<10000;i++){  //크기 변경
	array.push(i);
}

console.log("arraysize:"+array.length);
console.log('--------------------------------');

let arraymap = [];

console.log('arraymap start:'+arraymap.length);
console.time('map');

arraymap = array.map(list=>list);

console.log('arraymap end:'+arraymap.length);
console.timeEnd('map');
console.log('--------------------------------');


let arrayfor = [];
console.log('arrayfor start:'+arrayfor.length);
console.time('for');

for(let i=0; i<array.length;i++){
	arrayfor.push(array[i]);
};

console.log('arrayfor end:'+arrayfor.length);
console.timeEnd('for');
console.log('--------------------------------');

let arraywhile = [];
let whilenum = 0;
console.log('arraywhile start:'+arraywhile.length);
console.time('while');

while(whilenum<array.length){
	arraywhile.push(array[whilenum]);
	whilenum++;
};

console.log('arraywhile end:'+arraywhile.length);
console.timeEnd('while');
console.log('--------------------------------');

const arrayforof = [];
console.log('arrayforof start:'+arrayforof.length);
console.time('forof');

for(const num of array){
	arrayforof.push(num)
};

console.log('arrayfor end:'+arrayforof.length);
console.timeEnd('forof');
console.log('--------------------------------');

let arrayforin = [];
console.log('arrayforin start:'+arrayforin.length);
console.time('forin');

for(const num in array){
	arrayforin.push(array[num]);
};

console.log('arrayforin end:'+arrayforin.length);
console.timeEnd('forin');


console.log('---------------end-----------------');