OpenStreetMap + Leaflet 做路徑移動呈顯

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 : '&copy; <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不到地圖。

執行畫面如下: