Spring MVC、Ajax、 Jqyery、OpenStreetMap、Leaflet。
因專案需求,自行專研了一些關於Location相關的工具與實作,趁這個機會將技術保留起來供日後做應用。
本篇主要是以OpenStreetMap為基礎,將地圖上的經緯度以動態的方式連起來做呈顯。
目的是要做到以下示意圖的功能:
由經緯度1移動到經緯度2時,經緯度1用藍色的點呈現,經緯度2與其移動的路徑用紅色的點呈現。
經緯度2移動到經緯度3時,經緯度2變成藍色,經緯度3與其移動的路徑用紅色現,依此類推。
我們會用資料結構的方式完成點與路徑的儲存,以上圖來說,經緯度1只有點沒有路徑,經緯度2有點也有路徑(移動到經緯度2的紅色線)。
首先,先完成Spring MVC部分的Code(路徑物件PathStatistics與Spring MVC Controller)。
public class PathStatistics {
//箭頭點
private double[] latLng;
//箭頭後的線
private double[][] pathLatlng;
public double[] getLatLng() {
return latLng;
}
public void setLatLng(double[] latLng) {
this.latLng = latLng;
}
public double[][] getPathLatlng() {
return pathLatlng;
}
public void setPathLatlng(double[][] pathLatlng) {
this.pathLatlng = pathLatlng;
}
}
@RequestMapping(value = "/pathMoving", method = RequestMethod.POST)
ResponseBody
ublic List<PathStatistics> pathMoving() throws Exception {
List<PathStatistics> list = new ArrayList<PathStatistics>();
//設定連續的經緯度移動點, 以2維陣列的方式儲存
double[][] pathLatlngArr = new double[][]{{25.086610,121.488576},
{25.085917,121.489293},
{25.084916,121.488213},
{25.083316,121.489636},
{25.083020,121.489804},
{25.084061,121.491293},
{25.083793,121.491538},
{25.083664,121.491646},
{25.083534,121.491735},
{25.083287,121.491360},
{25.082907,121.490774},
{25.082700,121.490541},
{25.082457,121.490761},
{25.082240,121.490436},
{25.081815,121.490740},
{25.081710,121.490861},
{25.081945,121.491281},
{25.081556,121.491558},
{25.081439,121.491630},
{25.081216,121.491209},
{25.080780,121.491559},
{25.080635,121.491304},
{25.080869,121.491072}};
//宣告路徑物件
PathStatistics[] pp = new PathStatistics[pathLatlngArr.length];
//將路徑物件的內容塞入
for(int i=0 ; i< pp.length ; i++) {
pp[i] = new PathStatistics();
pp[i].setLatLng(pathLatlngArr[i]);
//從第2個點開始塞入路徑, 路徑資訊即為第1與第2個經緯度
if(i>0) {
double[][] pathLatlng = new double[][]{pathLatlngArr[i],pathLatlngArr[i-1]};
pp[i].setPathLatlng(pathLatlng);
}
list.add(pp[i]);
}
return list;
}
完成後,撰寫JSP與JavaScript。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/leaflet.css" />
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/js/jquery-ui/jquery-ui.css" />
<script src="${pageContext.request.contextPath}/static/js/leaflet.js"></script>
<script src="${pageContext.request.contextPath}/static/js/jquery-ui/external/jquery/jquery.js"></script>
<script src="${pageContext.request.contextPath}/static/js/jquery-ui/jquery-ui.js"></script>
<script src="${pageContext.request.contextPath}/static/js/pathMovingSample.js"></script>
</head>
<body>
<div>
<div id="map" style="height: 600px; width: 600px"></div>
</div>
<div>
<input type="button" id="btnDraw" name="btnDraw" value="開始畫圖"
onclick="javascript:startQuery()">
</div>
<div id="pathSlideDiv" style="display : none">
<div id="slider-timeline" style="width: 600px"></div>
<p>
<p>
<p>
<input type="button" id="btnAutoPlay" name="btnAutoPlay" value="GO"
onclick="javascript:autoPlay()">
<input type="button" id="btnStop" name="btnStop" value="STOP"
onclick="javascript:stopPlay()">
</div>
</body>
</html>
var latlongs;
var map;
var tiles;
var tkuLayers;
var tkuIntervalId;
var pathStatistics;
$(document).ready(function() {
map = L.map('map').setView([ 25.0411, 121.5646 ], 17);
tiles = L
.tileLayer(
'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
{
attribution : '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
}).addTo(map);
latlongs = [];
tkuLayers = [];
});
function startQuery() {
$.ajax({
url:'/spring_ryuichi/pathMoving',
type:"post",
//dataType (default: Intelligent Guess (xml, json, script, or html))
processData:false,
contentType:false,
success:function(data) {
cleanAllObject(true);
pathStatistics = data;
if(data.length > 0) {
drawPath();
} else {
$("#pathSlideDiv").hide();
alert("查無資料");
return;
}
},
error:function(data) {
alert("error " + data);
}
});
}
function drawPath() {
//畫第一個點
var orgPoint = L.circle(pathStatistics[0].latLng, {
color: 'red',
fillColor: 'red',
fillOpacity: 1,
radius: 10
}).addTo(map);//.binPopup().openPopup();
tkuLayers.push(orgPoint);
var latLng = L.latLng(pathStatistics[0].latLng);
map.flyTo(latLng);
initTimeLineSliderFullPath();
}
function initTimeLineSliderFullPath() {
if(pathStatistics != undefined && pathStatistics.length > 0) {
$("#slider-timeline").slider({
min : 1,
max : pathStatistics.length,
values : 1,
range : "min",
change : function(event, ui) {
drawFullContinuousPath(ui.value);
}
});
$("#pathSlideDiv").show();
}
}
function drawFullContinuousPath(value) {
//清空
cleanAllObject(false);
for(var start=0 ; start<value ; start++) {
//畫點
if(start>=0) {
if(start == (value-1)) {//目前最新的點, 畫紅色
var circlePoint = L.circle(pathStatistics[start].latLng, {
color: 'red',
fillColor: 'red',
fillOpacity: 1,
radius: 10
}).addTo(map);
tkuLayers.push(circlePoint);
//畫路線, 最新的線畫紅色
if(pathStatistics[start].pathLatlng != null) {
var line = L.polyline(pathStatistics[start].pathLatlng, { color: 'red' }).addTo(map);
tkuLayers.push(line);
}
} else {//已經過的點, 畫藍色
var circlePoint = L.circle(pathStatistics[start].latLng, {
color: 'blue',
fillColor: 'blue',
fillOpacity: 1,
radius: 10
}).addTo(map).bindPopup(pathStatistics[start].partialDateType);
tkuLayers.push(circlePoint);
//畫路線, 已畫過的線畫藍色
if(pathStatistics[start].pathLatlng != null) {
var line = L.polyline(pathStatistics[start].pathLatlng, { color: 'blue' }).addTo(map);
tkuLayers.push(line);
}
}
}
//畫最新的點時一併移動過去
var latlng = L.latLng(pathStatistics[start].latLng);
map.flyTo(latlng);
}
}
//自動播放路徑
function autoPlay() {
stopPlay();
var max = $("#slider-timeline").slider("option", "max");
var min = $("#slider-timeline").slider("option", "min");
var current = $("#slider-timeline").slider("value");
if(current == max) {
$("#slider-timeline").slider("value", min);
current = min;
}
tkuIntervalId = setInterval(frame, 1000);
function frame() {
if(current == max) {
clearInterval(tkuIntervalId);
} else {
current++;
$("#slider-timeline").slider("value", current);
}
}
}
//清除自動撥放
function stopPlay() {
clearInterval(tkuIntervalId);
}
//重新畫路經時, 清除之前畫過的物件
function cleanAllObject(cleanSlider) {
if(tkuLayers.length > 0) {
for(var idx = tkuLayers.length -1; idx >= 0 ; idx--) {
tkuLayers[idx].remove();
}
tkuLayers = [];
if(cleanSlider) {
stopPlay();
$("#slider-timeline").slider("destroy");
}
}
}
所有的細節都已在程式碼中的註解清楚的說明,各位可自行參考。
原則上就是搭配JQuery的slider物件完成動態呈現,唯一需注意的是,在loading map相關物件時,最好在$(document).ready(function(){}完成,避免執行速度不一造成load不到地圖。
執行畫面如下: