현재 뷰 지리좌표 구하기 ➡️ Polygon 구하기 ➡️ 이를 바탕으로 주소정보 필터링하기
카카오맵 API 중에 도로명 주소를 위경도 좌표로 변환하는 API가 있긴 하지만, 지도에 그리는 작업을 할 때 좌표로 변환하는 작업까지 하면 그만큼 시간이 소요됐다. 그래서 애초에 DB에 저장할 때 좌표정보까지 저장했다.
지리공간 쿼리를 사용하기 위해서는 정해진 Schema 형식이 있다.
Point는 location type 중에 하나로, 좌표(coordinates)가 단일 위치임을 의미한다(= 점 하나). Point 외에 MultiPoint, LineString, MultiLineString이 있지만 가장 기본이 되는 Point를 많이 사용한다.
지리공간 쿼리의 연산자를 사용하려면 우선 해당 필드에 index를 추가해서 GeoJSON 객체(= 지리적 지점과 폴리곤을 저장하기 위한 형식)로 지정해준다. location이라는 필드의 index를 2dsphere
로 지정한다.
import mongoose, { Schema } from 'mongoose';
const restaurantSchema = new Schema({
...,
location: {
type: {
type: String, // { location: { type: String } } 아님!
enum: ['Point'], // location type은 Point
required: true,
},
coordinates: {
type: [Number],
required: true,
},
},
});
restaurantSchema.index({ location: '2dsphere' }); // 인덱스 지정
const Restaurant = mongoose.model('Restaurant', restaurantSchema);
카카오맵의 bounds_changed 이벤트를 통해 중심좌표를 이동하면 북동위도(neLat), 북동경도(neLng), 남서위도(swLat), 남서경도(swLng)를 구할 수 있다.
getLists라는 함수는 이 좌표로 만든 Polygon(다각형)을 매개변수로 보내서 지리공간 쿼리로 필터링한 식당 리스트를 반환하도록 만들었다.
kakao.maps.event.addListener(map, 'bounds_changed', function () {
const neLat = map.getBounds().getNorthEast().getLat();
const neLng = map.getBounds().getNorthEast().getLng();
const swLat = map.getBounds().getSouthWest().getLat();
const swLng = map.getBounds().getSouthWest().getLng();
getLists([
[neLng, neLat],
[swLng, neLat],
[swLng, swLat],
[neLng, swLat],
[neLng, neLat],
]).then((res) => {
paintMarker(res);
store.dispatch({
type: 'CHANGED_CENTER',
COUNT: res.length,
});
});
});
...
export async function getLists(polygon: Polygon) {
const data = await fetch(`http://localhost:5000/api/maps/inner`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(polygon),
});
const res = await data.json();
return res;
}
위 좌표로 다각형(Polygon)을 만들어 준다. 이때 올바른 다각형을 만들기 위해 시작점과 끝점이 만나도록 해줘야 한다.
Polygon은 몽고DB의 지리공간 쿼리 중 면적을 지정하기 위해 필요한 값이다. DB에 있는 많은 데이터를 프론트에서 순서대로 읽어서 필터링 하는 것보다 DB의 기능으로 필터링하는 것이 더 빠르기 때문에 몽고DB의 Geospatial Query를 사용한다.
[neLng, neLat], [swLng, neLat], [swLng, swLat], [neLng, swLat], [neLng, neLat]
$geoWithin : 지정된 모양 내 완전히 존재하는 지리공간 데이터가 있는 document를 선택한다. 이때 지정된 모양은 GeoJSON Polygon, MultiPolygon, 레거시 좌표쌍이 가능하다.
$geometry : $geoWithin과 함께 사용할 GeoJSON geometry(도형)를 지정한다.
import { Router, Request, Response } from 'express';
import Restaurant from '../../models/Restaurant.js';
const route = Router();
export default (app: Router) => {
app.use('/maps', route);
route.post('/inner', async (req: Request, res: Response) => {
try {
const lists = await Restaurant.find({
location: {
$geoWithin: {
$geometry: {
type: 'Polygon',
coordinates: [req.body],
},
},
},
});
res.json(lists);
} catch (err) {
console.error(err);
}
});
};