🍁카카오맵 API
Open API > map
- Google Map
- Naver Map
- Kakao Map
게시판 구현에서 이어진다.
Ajax와 EL, JSTL을 사용한다.
초기설정
카카오맵 API
위 사이트를 참고하여 카카오맵 API를 구현해 보도록 하자.
카카오 개발자사이트
카카오톡 개발자 페이지에서 회원가입하고 로그인한다.
애플리케이션 추가하기
애플리케이션 추가하기 버튼을 눌러 위 내용을 입력한다.
이제 플랫폼에 따라서 사용할 수 있는 키를 얻을 수 있다. 이때 생성된 JavaScript키를 사용한다.
이제 안드로이드, 아이폰, 웹사이트 등 어느 플랫폼에서 서비스할지 설정해 주어야 한다.
Web 플랫폼 등록
http://localhost:8090
Web에서 운영할 것이므로 Web 플랫폼에 도메인 주소를 입력하면 된다.
파일 생성
java
com.test.toy.map > Ex.java
jsp
views > map > ex01.jsp
🍁지도 구현
com.test.toy.map
Ex.java
package com.test.toy.map;
import java.io.IOException;
import java.util.ArrayList;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/map/ex.do")
public class Ex extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//ex.do?no=01 > ex01.jsp
//ex.do?no=02 > ex02.jsp
String no = req.getParameter("no");
if (no.equals("03")) {
//좌표 가져오기
MapDAO dao = new MapDAO();
ArrayList<MapDTO> list = dao.list();
req.setAttribute("list", list);
}
RequestDispatcher dispatcher = req.getRequestDispatcher("/WEB-INF/views/map/ex" + no + ".jsp");
dispatcher.forward(req, resp);
}
}
서블릿은 번호만 바꿔가면서 사용할 수 있도록 프로그래밍한다.
map
ex01.jsp
지도를 담을 영역 만들기
<div>
<div id="map" style="width:768px;height:400px;"></div>
</div>
지도를 담을 영역을 <div> 태그로 한 번 더 감싸준다.
실제 지도를 그리는 Javascript API를 불러오기
<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=발급받은 APP KEY를 넣으시면 됩니다."></script>
지도를 담을 영역을 만들어야 한다. 지도를 출력할 태그는 div이다.
그리고 실제 지도를 그리는 JavaScript API를 불러온다. 이 코드는 실행 코드보다 먼저 선언되어야 한다.
지도를 띄우는 코드 작성
const container = document.getElementById('map'); //지도를 담을 영역의 DOM 레퍼런스
const options = { //지도를 생성할 때 필요한 기본 옵션
center : new kakao.maps.LatLng(33.450701, 126.570667), //지도의 중심좌표.
level : 3 //지도의 레벨(확대, 축소 정도)
};
const map = new kakao.maps.Map(container, options); //지도 생성 및 객체 리턴
코드의 선언부를 상수로 바꾸면서 요즘 스타일로 바꿔주었다.
전체코드
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="http://pinnpublic.dothome.co.kr/cdn/example-min.css">
<style>
</style>
</head>
<body>
<h1>Map <small>기본 지도</small></h1>
<div>
<div id="map" style="width:768px;height:400px;"></div>
</div>
<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=key"></script>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="http://pinnpublic.dothome.co.kr/cdn/example-min.js"></script>
<script>
const container = document.getElementById('map'); //지도를 담을 영역의 DOM 레퍼런스
const options = { //지도를 생성할 때 필요한 기본 옵션
center : new kakao.maps.LatLng(33.450701, 126.570667), //지도의 중심좌표.
level : 10 //지도의 레벨(확대, 축소 정도)
};
const map = new kakao.maps.Map(container, options); //지도 생성 및 객체 리턴
</script>
</body>
</html>
카카오톡 본사의 위치가 기본으로 설정되어 있는 것을 확인할 수 있다.
ex02.jsp
이번에는 버튼을 클릭하면 해당 위치로 바로 이동하는 기능을 구현해 보도록 하자.
https://www.google.co.kr/maps/?hl=ko
지도의 위도 경도를 찾을 때에는 Google 지도를 사용한다.
좌표 객체 생성
$('#btn1').click(function())
//좌표 객체
const p1 = new kakao.maps.LatLng(37.500178, 127.035017); //수원역 좌표
map.setCenter(p1);
}
좌표 객체를 생성하고 setCenter 메서드는 지도의 중앙값을 바꿔줄 수 있다.
setCenter 메서드는 순간이동 메서드고, panTo는 스크롤 이동 느낌의 패닝을 주는 메서드이다.
해당 좌표가 눈에 보이지 않으면 순간 이동을 하고, 너무 멀리 보이지 않는 이상 패닝을 주면서 이동한다.
이외에도 토글 버튼을 생성하여 확대 축소 기능, 클릭 기능을 구현할 수 있다.
전체 코드
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="http://pinnpublic.dothome.co.kr/cdn/example-min.css">
<style>
</style>
</head>
<body>
<h1>Map <small>지도 좌표 이동하기 + 레벨 바꾸기</small></h1>
<div>
<div id="map" style="width:768px;height:400px;"></div>
</div>
<hr>
<div>
<input type="button" value="수원역으로 이동" id="btn1">
<input type="button" value="역삼역으로 이동" id="btn2">
<input type="button" value="탄현역으로 이동" id="btn3">
</div>
<div>
<input type="button" value="확대" id="btn4">
<input type="button" value="축소" id="btn5">
</div>
<div>
<input type="button" value="드래그 On Off" id="btn6">
<input type="button" value="확대 On Off" id="btn7">
</div>
<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=key"></script>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="http://pinnpublic.dothome.co.kr/cdn/example-min.js"></script>
<script>
const container = document.getElementById('map'); //지도를 담을 영역의 DOM 레퍼런스
const options = { //지도를 생성할 때 필요한 기본 옵션
center : new kakao.maps.LatLng(37.499322, 127.033157), //지도의 중심좌표.
level : 5 //지도의 레벨(확대, 축소 정도)
};
const map = new kakao.maps.Map(container, options); //지도 생성 및 객체 리턴
//수원역 37.266440, 127.000609
//역삼역 37.500178, 127.035017
//탄현역 37.693773, 126.761717
$('#btn1').click(function() {
//좌표 객체
const p1 = new kakao.maps.LatLng(37.266440, 127.000609); //수원역 좌표
map.setCenter(p1);
map.panTo(p1);
});
$('#btn2').click(function() {
//좌표 객체
const p1 = new kakao.maps.LatLng(37.500178, 127.035017); //역삼역 좌표
map.setCenter(p1);
});
$('#btn3').click(function() {
//좌표 객체
const p1 = new kakao.maps.LatLng(37.693773, 126.761717); //탄현역 좌표
map.setCenter(p1);
});
$('#btn4').click(function() {
//map.setLevel(1);
map.setLevel(map.getLevel() - 1); //줌 인
});
$('#btn5').click(function() {
//map.setLevel(14);
map.setLevel(map.getLevel() + 1); //줌 인
});
$('#btn6').click(function() {
//토글 버튼(드래그 On/Off))
if (map.getDraggable()) {
map.setDraggable(false);
$(this).css('background-color', '#EFEFEF');
} else {
map.setDraggable(true);
$(this).css('background-color', 'gold');
}
});
$('#btn7').click(function() {
//토글 버튼(확대 On/Off))
if (map.getZoomable()) {
map.setZoomable(false);
$(this).css('background-color', '#EFEFEF');
} else {
map.setZoomable(true);
$(this).css('background-color', 'gold');
}
});
</script>
</body>
</html>
🍁마커 구현
ex03.jsp
지도에 클릭 이벤트를 걸어서 위도, 경도를 알아내고 마커를 추가해 보도록 하자.
이벤트 추가
/*
map.onclick = function() {
alert();
};
*/
map은 눈에 보이는 지도 역할을 하지만, 일반적인 DOM 객체가 아니므로 이벤트가 추가되지 않는다.
대신에 카카오맵에서 자체적으로 제공하는 메서드를 사용하여 이벤트를 추가한다.
//kakao.maps.event.addListener(지도객체, 이벤트, 콜백함수);
kakao.maps.event.addListener(map, 'click', function(evt) {
//alert(evt.latLng); //좌표 객체
let message = `클릭한 위치의 위도 경도는 (\${evt.latLng.getLat()}, \${evt.latLng.getLng()}) 입니다`;
$('.message').text(message);
});
이벤트 관련 정보는 evt 객체가 제공한다.
마커 등록하기
const m1 = new kakao.maps.Marker({
position: new kakao.maps.LatLng(evt.latLng.getLat(), evt.latLng.getLng())
});
이제 마커라는 오브젝트를 만들 수 있다.
이때 문장이 아니라 프로퍼티이므로 세미콜론을 찍지 않는다.
새로고침하면 마커가 사라지는데, 영구적으로 보관하도록 해보자.
ddl.sql
-- 마커 저장 테이블
drop table tblMarker;
drop sequence seqMarker;
create table tblMarker (
seq number primary key, --PK
lat number not null, --위도(latitude)
lng number not null --경도(longitude)
);
create sequence seqMarker;
마커를 저장할 테이블을 생성하였다.
마커를 추가하면 테이블에 마커를 저장하여 새로고침하더라도 계속 출력하도록 한다.
마커 저장하기
MapDAO.java
package com.test.toy.map;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import com.test.toy.DBUtil;
public class MapDAO {
private Connection conn;
private Statement stat;
private PreparedStatement pstat;
private ResultSet rs;
public MapDAO() {
this.conn = DBUtil.open();
}
public int add(MapDTO dto) {
//queryParamNoReturn
try {
String sql = "insert into tblMarker (seq, lat, lng) values (seqMarker.nextVal, ?, ?)";
pstat = conn.prepareStatement(sql);
pstat.setString(1, dto.getLat());
pstat.setString(2, dto.getLng());
return pstat.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
public ArrayList<MapDTO> list() {
//queryNoParamListReturn
try {
String sql = "select * from tblMarker";
stat = conn.createStatement();
rs = stat.executeQuery(sql);
ArrayList<MapDTO> list = new ArrayList<MapDTO>();
while (rs.next()) {
MapDTO dto = new MapDTO();
dto.setSeq(rs.getString("seq"));
dto.setLat(rs.getString("lat"));
dto.setLng(rs.getString("lng"));
list.add(dto);
}
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
MapDTO.java
package com.test.toy.map;
import lombok.Data;
@Data
public class MapDTO {
private String seq;
private String lat;
private String lng;
}
AddMarker.java
package com.test.toy.map;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONObject;
@WebServlet("/map/addmarker.do")
public class AddMarker extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 위도와 경도 받아오기
String lat = req.getParameter("lat");
String lng = req.getParameter("lng");
// 2. DAO에 의뢰 및 DTO에 포장
MapDAO dao = new MapDAO();
MapDTO dto = new MapDTO();
dto.setLat(lat);
dto.setLng(lng);
int result = dao.add(dto);
// 3. 피드백
resp.setContentType("application/json");
JSONObject obj = new JSONObject();
obj.put("result", result);
PrintWriter writer = resp.getWriter();
writer.write(obj.toString());
writer.close();
}
}
전체 코드
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="http://pinnpublic.dothome.co.kr/cdn/example-min.css">
<style>
</style>
</head>
<body>
<h1>Map <small>지도 클릭 이벤트 + 마커 조작</small></h1>
<div>
<div id="map" style="width:768px;height:400px;"></div>
</div>
<div>
<div class="message"></div>
</div>
<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=key"></script>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="http://pinnpublic.dothome.co.kr/cdn/example-min.js"></script>
<script>
const container = document.getElementById('map'); //지도를 담을 영역의 DOM 레퍼런스
const options = { //지도를 생성할 때 필요한 기본 옵션
center : new kakao.maps.LatLng(37.500178, 127.035017), //지도의 중심좌표.
level : 5 //지도의 레벨(확대, 축소 정도)
};
const map = new kakao.maps.Map(container, options); //지도 생성 및 객체 리턴
//이벤트 추가
//kakao.maps.event.addListener(지도객체, 이벤트, 콜백함수);
kakao.maps.event.addListener(map, 'click', function(evt) {
//alert(evt.latLng); //좌표 객체
let message = `클릭한 위치의 위도 경도는 (\${evt.latLng.getLat()}, \${evt.latLng.getLng()}) 입니다`;
$('.message').text(message);
//현재 좌표를 Ajax로 서버로 전송해 insert 작업 실행
$.ajax({
type: 'POST',
url: '/toy/map/addmarker.do',
data: {
lat: evt.latLng.getLat(),
lng: evt.latLng.getLng()
},
dataType: 'json',
success: function(result) {
//내가 클릭한 위치가 DB로 넘어가고 1을 반환하면 마커를 화면에 보여준다.
if (result.result == 1) {
//마커 등록하기
const m1 = new kakao.maps.Marker({
position: new kakao.maps.LatLng(evt.latLng.getLat(), evt.latLng.getLng()) //문장이 아니라 프로퍼티이므로 세미콜론을 찍지 않는다.
});
//마커를 지도에 추가하기
m1.setMap(map);
} else {
alert('failed');
}
},
error: function(a,b,c) {
console.log(a,b,c);
}
});
}); //click
//DB에서 좌표를 가져와 마커 출력
<c:forEach items="${list}" var="dto" varStatus="status">
//마커 등록하기
const m${status.count} = new kakao.maps.Marker({
position: new kakao.maps.LatLng(${dto.lat}, ${dto.lng}) //EL로 출력
});
//마커를 지도에 추가하기
m${status.count}.setMap(map);
</c:forEach>
</script>
</body>
</html>
주의사항
//DB에서 좌표를 가져와 마커 출력
<c:forEach items="${list}" var="dto">
//마커 등록하기
const m1 = new kakao.maps.Marker({
position: new kakao.maps.LatLng(evt.latLng.getLat(), evt.latLng.getLng()) //문장이 아니라 프로퍼티이므로 세미콜론을 찍지 않는다.
});
//마커를 지도에 추가하기
m1.setMap(map);
</c:forEach>
상수(const)로 선언하면 에러가 발생한다.
변수로 선언할 수도 있지만, ${status.count}를 사용해 충돌을 피할 수 있다.
🍁즐겨찾기 구현
등록한 마커에 이름을 붙여 즐겨찾기를 구현해보도록 하자.
JSON, 이벤트 버블링은 위의 글을 참고한다.
ddl.sql
-- 마커 저장 테이블
drop table tblMarker;
drop sequence seqMarker;
create table tblMarker (
seq number primary key, --PK
lat number not null, --위도(latitude)
lng number not null --경도(longitude)
);
create sequence seqMarker;
-- 장소 테이블
drop table tblMarker;
drop sequence seqMarker;
create table tblPlace (
seq number primary key, --PK
lat number not null, --위도(latitude)
lng number not null, --경도(longitude)
name varchar2(100) not null, --장소명
category varchar2(100) default 'default' not null --장소분류
);
create sequence seqPlace;
PlaceDTO.java
package com.test.toy.map;
import lombok.Data;
@Data
public class PlaceDTO {
private String seq;
private String lat;
private String lng;
private String name;
private String category;
}
두 개 이상의 테이블을 하나의 DTO로 만드는 경우는 없으므로 DTO를 새로 만들었다.
AddPlace.java
package com.test.toy.map;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONObject;
@WebServlet("/map/addplace.do")
public class AddPlace extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//AddPlace.java
//1.
String lat = req.getParameter("lat");
String lng = req.getParameter("lng");
String name = req.getParameter("name");
String category = req.getParameter("category");
//2.
MapDAO dao = new MapDAO();
PlaceDTO dto = new PlaceDTO();
dto.setLat(lat);
dto.setLng(lng);
dto.setName(name);
dto.setCategory(category);
int result = dao.addPlace(dto);
//3.
resp.setContentType("application/json");
JSONObject obj = new JSONObject();
obj.put("result", result);
PrintWriter writer = resp.getWriter();
writer.write(obj.toString());
writer.close();
}
}
ListPlace.java
package com.test.toy.map;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
@WebServlet("/map/listplace.do")
public class ListPlace extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.
MapDAO dao = new MapDAO();
ArrayList<PlaceDTO> list = dao.listPlace();
//2. list > JSONArray
resp.setContentType("application/json");
resp.setCharacterEncoding("UTF-8");
//ArrayList DTO를 JSON타입으로 변경
JSONArray arr = new JSONArray();
//원본을 탐색하면서 장소에 대한 데이터를 가져와 dto 1개를 jsonObject 1개로 변환
//JSON 형식의 문자열로 만들기 번거롭기 때문에 JSON 형태의 객체에 넣어주면 덤프를 쉽게 해주기 때문에 이와 같은 작업을 한다.
for (PlaceDTO dto: list) {
//DTO > JSONObject
JSONObject obj = new JSONObject();
obj.put("seq", dto.getSeq());
obj.put("lat", dto.getLat());
obj.put("lng", dto.getLng());
obj.put("name", dto.getName());
obj.put("category", dto.getCategory());
arr.add(obj);
}
PrintWriter writer = resp.getWriter();
writer.write(arr.toString());
writer.close();
}
}
DelPlace.java
package com.test.toy.map;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.simple.JSONObject;
@WebServlet("/map/delplace.do")
public class DelPlace extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.
String seq = req.getParameter("seq");
//2.
MapDAO dao = new MapDAO();
int result = dao.delPlace(seq);
//3.
resp.setContentType("application/json");
JSONObject obj = new JSONObject();
obj.put("result", result);
PrintWriter writer = resp.getWriter();
writer.write(obj.toString());
writer.close();
}
}
MapDAO.java
public ArrayList<PlaceDTO> listPlace() {
//queryNoParamListReturn
try {
String sql = "select * from tblPlace";
stat = conn.createStatement();
rs = stat.executeQuery(sql);
ArrayList<PlaceDTO> list = new ArrayList<PlaceDTO>();
while (rs.next()) {
PlaceDTO dto = new PlaceDTO();
dto.setSeq(rs.getString("seq"));
dto.setLat(rs.getString("lat"));
dto.setLng(rs.getString("lng"));
dto.setName(rs.getString("name"));
dto.setCategory(rs.getString("category"));
list.add(dto);
}
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public int delPlace(String seq) {
//queryParamNoReturn
try {
String sql = "delete from tblPlace where seq = ?";
pstat = conn.prepareStatement(sql);
pstat.setString(1, seq);
return pstat.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
ex04.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="http://pinnpublic.dothome.co.kr/cdn/example-min.css">
<style>
#main {
display: flex;
align-items: flex-start;
}
#main table {
width: 384px;
margin: 0px 16px;
margin-bottom: 10px;
}
#list td {
cursor: pointer;
/*
display: flex;
justify-content: space-between;
td는 display가 반드시 table-cell이어야 한다. flex가 되는 순간 div가 되며 레이아웃이 깨진다.
*/
}
#list td span:Last-child {
float: right;
display: none;
}
#list td:hover span:Last-child {
display: inline;
}
</style>
</head>
<body class="wide">
<h1>Map <small>즐겨찾기(CRD)</small></h1>
<div id="main">
<div id="map" style="width:768px;height:400px;"></div>
<div>
<table>
<tr>
<td>
<select name="category" id="category">
<option value="default">기본</option>
<option value="cafe">카페</option>
<option value="food">음식점</option>
<option value="private">개인</option>
</select>
<input type="text" name="name" id="name" class="middle">
<input type="button" value="추가하기" id="btn">
</td>
</tr>
</table>
<table id="list">
<tbody></tbody>
<!--
<tr>
<td>AAA</td>
</tr>
-->
</table>
</div>
</div>
<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=2b631c4003121607c7cb22ff2f41d62f"></script>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="http://pinnpublic.dothome.co.kr/cdn/example-min.js"></script>
<script>
const container = document.getElementById('map'); //지도를 담을 영역의 DOM 레퍼런스
const options = { //지도를 생성할 때 필요한 기본 옵션
center : new kakao.maps.LatLng(37.499322, 127.033157), //지도의 중심좌표.
level : 5 //지도의 레벨(확대, 축소 정도)
};
const map = new kakao.maps.Map(container, options); //지도 생성 및 객체 리턴
//클릭 이벤트 + 원하는 장소에 마커 추가하기(DB 작업 X)
let m = null;
let lat = null;
let lng = null;
//위도 경도가 발생하는 시점이 따로 있기 때문에 전역 변수로 선언
kakao.maps.event.addListener(map, 'click', function(evt) {
lat = evt.latLng.getLat();
lng = evt.latLng.getLng();
if (m != null) {
//기존 마커 제거
m.setMap(null);
}
//카테고리 확인 (마커 이미지 만들기)
//$('#category').val() > 아이콘 이미지명
//마커 이미지 객체를 만들고, src, size, option을 조정해준다.
//let markerImage = new kakao.maps.MarkerImage(src, size, option);
let imageUrl = '/toy/asset/marker/' + $('#category').val() + '.png'; //이미지 경로
let imageSize = new kakao.maps.Size(40, 40); //이미지 크기
let option = {
//spriteOrigin: new kakao.maps.Point(10, 20),
//spriteSize: new kakao.maps.Size(36, 98)
};//옵션은 객체타입으로 요구를 한다.
let markerImage = new kakao.maps.MarkerImage(imageUrl, imageSize, option);
m = new kakao.maps.Marker({
position: new kakao.maps.LatLng(lat, lng)
});
m.setImage(markerImage);
m.setMap(map);
$('#name').select();
});
$('#category').change(function() {
//마커가 있을 경우 아이콘 변경
if (m != null) {
let imageUrl = '/toy/asset/marker/' + $('#category').val() + '.png';
let imageSize = new kakao.maps.Size(40, 40);
let option = {};
let markerImage = new kakao.maps.MarkerImage(imageUrl, imageSize, option);
m.setImage(markerImage);
}
});
//추가하기
$('#btn').click(function() {
$.ajax({
type: 'POST',
url: '/toy/map/addplace.do',
data: {
lat: lat,
lng: lng,
name: $('#name').val(),
category: $('#category').val()
},
dataType: 'json',
success: function(result) {
if (result.result == 1) {
$('#category').val('default');
$('#name').val('');
$('#name').select();
//추가한 목록을 아래 테이블에 출력
//새로고침
load();
} else {
alert('failed');
}
},
error: function(a,b,c) {
console.log(a,b,c);
}
});
});
//출력하기
load(); //자바스크립트는 함수 호이스팅때문에 선언부 위에서 호출할 수 있다.
function load() {
$.ajax({
type: 'GET',
url: '/toy/map/listplace.do',
dataType: 'json',
success: function(result) {
$('#list tbody').html(''); //내용이 누적되지 않도록 초기화
//Template String으로 데이터 삽입
$(result).each((index, item) => {
//category는 데이터로 넘길 때 문자열이므로 ''안에 적는다.
$('#list tbody').append(`
<tr>
<td onclick="selPlace(\${item.lat}, \${item.lng}, '\${item.category}');">
<span>\${item.name}</span>
<span title="delete" onclick="delPlace(\${item.seq});">×</span>
</td>
</tr>
`);
});
},
error: function(a,b,c) {
console.log(a,b,c);
}
});
}
function selPlace(lat, lng, category) {
//해당 장소 > 위도, 경도 > 마커 출력하기
//alert(lat + ',' + lng);
//alert(category);
if (m != null) {
//기존 마커 제거
m.setMap(null);
}
//마커 이미지 추가
let imageUrl = '/toy/asset/marker/' + category + '.png';
let imageSize = new kakao.maps.Size(40, 40);
let option = {};
let markerImage = new kakao.maps.MarkerImage(imageUrl, imageSize, option);
//마커 생성
m = new kakao.maps.Marker({
position: new kakao.maps.LatLng(lat, lng)
});
m.setImage(markerImage);
m.setMap(map);
//map.setCenter()
map.panTo(new kakao.maps.LatLng(lat, lng));
$('#list td').css('background-color', 'transparent');
$(event.currentTarget).css('background-color', 'gold');
}
//삭제하기
function delPlace(seq) {
$.ajax({
type: 'POST',
url: '/toy/map/delplace.do',
data: {
seq: seq
//매개변수로 기본키를 넘겨받아 삭제할 번호를 받는다.
},
dataType: 'json',
success: function(result) {
if (result.result == 1) {
//장소 삭제 완료
if (m != null) {
//기존 마커 제거
m.setMap(null);
}
//목록 새로고침
load();
} else {
alert('failed');
}
},
error: function(a,b,c) {
console.log(a,b,c);
}
});
//버블 취소
event.stopPropagation();
event.cancelBubble = true;
//delPlace가 실행되는 위치는 selPlace위치이기도 하므로 명확하게 구분짓는 게 좋다.
}
</script>
</body>
</html>
🍁Marker 활용
서버와 연결하고 화면을 구성하여 마커를 화면에 출력하는 작업을 해보도록 하자.
그리고 마커에 클릭 이벤트를 걸어서 사용자 입력에 반응하도록 해보자.
sql
-- 장소 테이블
drop table tblPlace;
drop sequence seqPlace;
create table tblPlace (
seq number primary key, --PK
lat number not null, --위도(latitude)
lng number not null, --경도(longitude)
name varchar2(100) not null, --장소명
category varchar2(100) default 'default' not null --장소분류
);
create sequence seqPlace;
insert into tblPlace values (seqPlace.nextVal, 37.49946254149899, 127.03350821415333, '스타벅스 역삼포스코점', 'cafe');
insert into tblPlace values (seqPlace.nextVal, 37.5000214774923, 127.03238887639358, '블루보틀 역삼 카페', 'cafe');
insert into tblPlace values (seqPlace.nextVal, 37.4999672212306, 127.03309566275318, '할리스 역삼 테헤란점', 'cafe');
insert into tblPlace values (seqPlace.nextVal, 37.49971222207651, 127.03471555274336, '스타벅스 아크플레이스점', 'cafe');
insert into tblPlace values (seqPlace.nextVal, 37.49960495388392, 127.0316818901042, '스타벅스 국기원사거리점', 'cafe');
insert into tblPlace values (seqPlace.nextVal, 37.49909547011733, 127.03319423022872, '해머스미스 커피', 'cafe');
insert into tblPlace values (seqPlace.nextVal, 37.500404087936744, 127.03352559842763, '요술포차', 'food');
insert into tblPlace values (seqPlace.nextVal, 37.49817406332744, 127.03364616982121, '을밀대컵냉면', 'food');
insert into tblPlace values (seqPlace.nextVal, 37.49810897418154, 127.03281495289778, '우밀가 안동국시', 'food');
insert into tblPlace values (seqPlace.nextVal, 37.49963755476663, 127.03583509996064, '바스버거 역삼점', 'food');
insert into tblPlace values (seqPlace.nextVal, 37.50112907045147, 127.03465966304452, '호타루', 'food');
insert into tblPlace values (seqPlace.nextVal, 37.50108394690212, 127.03490844293854, '나주 곰탕', 'food');
insert into tblPlace values (seqPlace.nextVal, 37.499977809864845, 127.03541965834984, '신한은행 강남중앙점', 'private');
insert into tblPlace values (seqPlace.nextVal, 37.49925092460049, 127.0330840377675, 'IBK 기업은행 테헤란로지점', 'private');
insert into tblPlace values (seqPlace.nextVal, 37.49973763498563, 127.032482052401, 'GS25 강남N타워점', 'private');
insert into tblPlace values (seqPlace.nextVal, 37.50009046016296, 127.03533772148997, '역삼역 3번출구', 'default');
insert into tblPlace values (seqPlace.nextVal, 37.49809426246327, 127.02876361388746, '강남역 1번출구', 'default');
insert into tblPlace values (seqPlace.nextVal, 37.49839584023911, 127.02980978614593, '삼성서초역삼세무서', 'default');
select * from tblPlace;
commit;
Ex.java
package com.test.toy.map;
import java.io.IOException;
import java.util.ArrayList;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/map/ex.do")
public class Ex extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//ex.do?no=01 > ex01.jsp
//ex.do?no=02 > ex02.jsp
String no = req.getParameter("no");
if (no.equals("03")) {
//좌표 가져오기
MapDAO dao = new MapDAO();
ArrayList<MapDTO> list = dao.list();
req.setAttribute("list", list);
} else if (no.equals("05")) {
MapDAO dao = new MapDAO();
ArrayList<PlaceDTO> list = dao.listPlace();
//System.out.println(list);
req.setAttribute("list", list);
}
RequestDispatcher dispatcher = req.getRequestDispatcher("/WEB-INF/views/map/ex" + no + ".jsp");
dispatcher.forward(req, resp);
}
}
ex05.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="http://pinnpublic.dothome.co.kr/cdn/example-min.css">
<style>
#list {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
#list .item {
border: 1px solid #CCC;
border-radius: 5px;
padding: 5px 10px;
display: flex;
align-items: center;
}
#list .item img {
height: 35px;
margin: 7px;
}
</style>
</head>
<body class="wide">
<body class="wide">
<h1>Map <small>Marker</small></h1>
<div>
<div id="map" style="width:1168px;height:400px;"></div>
</div>
<!-- <div id="result"></div> -->
<hr>
<h3>Place</h3>
<div id="list">
<c:forEach items="${list}" var="dto">
<div class="item" id="item${dto.seq}">
<img src="/toy/asset/marker/${dto.category}.png">
<span>${dto.name}</span>
</div>
</c:forEach>
</div>
<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=d9a9dc5f180000f50bb124866e70f51a"></script>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="http://pinnpublic.dothome.co.kr/cdn/example-min.js"></script>
<script>
const container = document.getElementById('map');
const options = {
center: new kakao.maps.LatLng(37.499316, 127.033192),
level: 3
};
const map = new kakao.maps.Map(container, options);
const plist = [];
//마커 출력
const imageSize = new kakao.maps.Size(40, 40);
const option = {};
//Uncaught SyntaxError: Identifier 'm' has already been declared (at ex.do?no=05:152:9)
//m이 중복되어서 현재는 지도가 출력되지 않는 오류 발생 -> 상수 const에 넘버링 (동일한 변수를 여러 개 만들어도 오류가 발생하므로 이름을 다르게 함)
<c:forEach items="${list}" var="dto" varStatus="status">
const imageUrl${status.count} = '/toy/asset/marker/${dto.category}.png';
const markerImg${status.count} = new kakao.maps.MarkerImage(imageUrl${status.count}, imageSize, option);
const m${status.count} = new kakao.maps.Marker({
position: new kakao.maps.LatLng(${dto.lat}, ${dto.lng})
});
m${status.count}.setImage(markerImg${status.count}); //마커 이미지 수정
m${status.count}.seq = ${dto.seq}; //시퀀스 프로퍼티를 만들어서 DB에서 가져온 PK를 붙였다.
m${status.count}.setMap(map);
//마커 + 클릭 이벤트
//kakao.maps.event.addListener(대상, 종류, 콜백함수);
//1번부터 18번마커 대상으로 콜백 클릭 함수를 걸어준다.
kakao.maps.event.addListener(m${status.count}, 'click', function(evt) {
//누구를 클릭한걸까?
//기본키(PK)를 알아내야 한다.
//alert(event.target);
//alert(event.srcElemet);
//alert(event.currentTarget);
//alert(this);
//보통 이 4개중에 하나는 걸린다. event를 evt로 변경하여 확인해본다.
//alert(evt.target); > X
//alert(this.target.nodeName); > X
//alert(evt.srcElemet); > X
//alert(evt.currentTarget); > X
//alert(this.nodeName); > O undefined로 나오긴 하지만 this.setMap(null)를 했을 때 사라지는 걸 보면 this가 클릭한 마커가 맞긴 하다.
//*** alert(this.seq); > O 자바스크립트의 특징을 이용해 시퀀스 프로퍼티를 만들어서 클릭한 태그를 확인한다.
selPlace(this.seq);
$('#map > div > div > div > div > img').css('opacity', '.3');
$(event.target).css('opacity', '1');
});
//plist 배열에 장소 정보 추가
plist.push({
seq: ${dto.seq},
lat: ${dto.lat},
lng: ${dto.lng}
});
</c:forEach>
function selPlace(seq) {
$('#list .item').css('background-color', 'transparent');
$('#list #item' + seq).css('background-color', 'gold');
}
function clear() {
$('#map > div > div > div > div > img').css('opacity', '1');
$('#list .item').css('background-color', 'transparent');
}
window.onkeydown = function() {
if (event.keyCode == 27) {
//esc
clear();
}
};
kakao.maps.event.addListener(map, 'click', function(evt) {
clear();
});
kakao.maps.event.addListener(map, 'dragend', function(evt) {
//alert();
//현재 출력되는 지도의 영역을 반환
//$('#result').text(map.getBounds()); //((37.49755416353572, 127.02660911977316), (37.50115447406129, 127.03981946791819))
//$('#result').text(map.getBounds().getSouthWest()); //지도의 남서쪽
//$('#result').text(map.getBounds().getSouthWest()); //지도의 북동쪽
//이 데이터를 가지고 화면에 마커가 있는지 없는지를 확인할 수 있다. 이에 해당하는 장소를 출력하고, 화면에서 보이지 않는 장소는 출력에서 제외한다.
$('#result').text('');
$(plist).each((index, item) => {
if (contains(item.lat, item.lng)) {
//$('#result').append('포함, ');
$('#list #item' + item.seq).show();
} else {
//$('#result').append('미포함, ');
$('#list #item' + item.seq).hide();
}
});
});
//지도 > 줌인, 줌아웃
kakao.maps.event.addListener(map, 'zoom_changed', function(evt) {
$('#result').text('');
$(plist).each((index, item) => {
if (contains(item.lat, item.lng)) {
$('#list #item' + item.seq).show();
} else {
$('#list #item' + item.seq).hide();
}
});
});
function contains(lat, lng) {
const sw = map.getBounds().getSouthWest();
const ne = map.getBounds().getNorthEast();
if (lat >= sw.getLat() && lat <= ne.getLat()
&& lng >= sw.getLng() && lng <= ne.getLng()) {
return true;
} else {
return false;
}
}
</script>
</body>
</html>