原文链接及内容

运行界面

一个移动(手机)设备定位的例子。

main.js代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
import Geolocation from '../src/ol/Geolocation.js';
import LineString from '../src/ol/geom/LineString.js';
import Map from '../src/ol/Map.js';
import OSM from '../src/ol/source/OSM.js';
import Overlay from '../src/ol/Overlay.js';
import TileLayer from '../src/ol/layer/Tile.js';
import View from '../src/ol/View.js';
import {fromLonLat} from '../src/ol/proj.js';

// creating the view
const view = new View({
center: fromLonLat([5.8713, 45.6452]),
zoom: 19,
});

const tileLayer = new TileLayer({
source: new OSM(),
});

// creating the map
const map = new Map({
layers: [tileLayer],
target: 'map',
view: view,
});

// Geolocation marker
const markerEl = document.getElementById('geolocation_marker');
const marker = new Overlay({
positioning: 'center-center',
element: markerEl,
stopEvent: false,
});
map.addOverlay(marker);

// LineString to store the different geolocation positions. This LineString
// is time aware.
// The Z dimension is actually used to store the rotation (heading).
const positions = new LineString([], 'XYZM');

// Geolocation Control
const geolocation = new Geolocation({
projection: view.getProjection(),
trackingOptions: {
maximumAge: 10000,
enableHighAccuracy: true,
timeout: 600000,
},
});

let deltaMean = 500; // the geolocation sampling period mean in ms

// Listen to position changes
geolocation.on('change', function () {
const position = geolocation.getPosition();
const accuracy = geolocation.getAccuracy();
const heading = geolocation.getHeading() || 0;
const speed = geolocation.getSpeed() || 0;
const m = Date.now();

addPosition(position, heading, m, speed);

const coords = positions.getCoordinates();
const len = coords.length;
if (len >= 2) {
deltaMean = (coords[len - 1][3] - coords[0][3]) / (len - 1);
}

const html = [
'Position: ' + position[0].toFixed(2) + ', ' + position[1].toFixed(2),
'Accuracy: ' + accuracy,
'Heading: ' + Math.round(radToDeg(heading)) + '°',
'Speed: ' + (speed * 3.6).toFixed(1) + ' km/h',
'Delta: ' + Math.round(deltaMean) + 'ms',
].join('<br />');
document.getElementById('info').innerHTML = html;
});

geolocation.on('error', function () {
alert('geolocation error');
// FIXME we should remove the coordinates in positions
});

// convert radians to degrees
function radToDeg(rad) {
return (rad * 360) / (Math.PI * 2);
}
// convert degrees to radians
function degToRad(deg) {
return (deg * Math.PI * 2) / 360;
}
// modulo for negative values
function mod(n) {
return ((n % (2 * Math.PI)) + 2 * Math.PI) % (2 * Math.PI);
}

function addPosition(position, heading, m, speed) {
const x = position[0];
const y = position[1];
const fCoords = positions.getCoordinates();
const previous = fCoords[fCoords.length - 1];
const prevHeading = previous && previous[2];
if (prevHeading) {
let headingDiff = heading - mod(prevHeading);

// force the rotation change to be less than 180°
if (Math.abs(headingDiff) > Math.PI) {
const sign = headingDiff >= 0 ? 1 : -1;
headingDiff = -sign * (2 * Math.PI - Math.abs(headingDiff));
}
heading = prevHeading + headingDiff;
}
positions.appendCoordinate([x, y, heading, m]);

// only keep the 20 last coordinates
positions.setCoordinates(positions.getCoordinates().slice(-20));

// FIXME use speed instead
if (heading && speed) {
markerEl.src = 'data/geolocation_marker_heading.png';
} else {
markerEl.src = 'data/geolocation_marker.png';
}
}

// recenters the view by putting the given coordinates at 3/4 from the top or
// the screen
function getCenterWithHeading(position, rotation, resolution) {
const size = map.getSize();
const height = size[1];

return [
position[0] - (Math.sin(rotation) * height * resolution * 1) / 4,
position[1] + (Math.cos(rotation) * height * resolution * 1) / 4,
];
}

let previousM = 0;
function updateView() {
// use sampling period to get a smooth transition
let m = Date.now() - deltaMean * 1.5;
m = Math.max(m, previousM);
previousM = m;
// interpolate position along positions LineString
const c = positions.getCoordinateAtM(m, true);
if (c) {
view.setCenter(getCenterWithHeading(c, -c[2], view.getResolution()));
view.setRotation(-c[2]);
marker.setPosition(c);
map.render();
}
}

// geolocate device
const geolocateBtn = document.getElementById('geolocate');
geolocateBtn.addEventListener(
'click',
function () {
geolocation.setTracking(true); // Start position tracking

tileLayer.on('postrender', updateView);
map.render();

disableButtons();
},
false
);

// simulate device move
let simulationData;
const client = new XMLHttpRequest();
client.open('GET', 'data/geolocation-orientation.json');

/**
* Handle data loading.
*/
client.onload = function () {
simulationData = JSON.parse(client.responseText).data;
};
client.send();

const simulateBtn = document.getElementById('simulate');
simulateBtn.addEventListener(
'click',
function () {
const coordinates = simulationData;

const first = coordinates.shift();
simulatePositionChange(first);

let prevDate = first.timestamp;
function geolocate() {
const position = coordinates.shift();
if (!position) {
return;
}
const newDate = position.timestamp;
simulatePositionChange(position);
window.setTimeout(function () {
prevDate = newDate;
geolocate();
}, (newDate - prevDate) / 0.5);
}
geolocate();

tileLayer.on('postrender', updateView);
map.render();

disableButtons();
},
false
);

function simulatePositionChange(position) {
const coords = position.coords;
geolocation.set('accuracy', coords.accuracy);
geolocation.set('heading', degToRad(coords.heading));
const projectedPosition = fromLonLat([coords.longitude, coords.latitude]);
geolocation.set('position', projectedPosition);
geolocation.set('speed', coords.speed);
geolocation.changed();
}

function disableButtons() {
geolocateBtn.disabled = 'disabled';
simulateBtn.disabled = 'disabled';
}

界面布局文件index.html代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
<title>Mobile Geolocation Tracking with Orientation</title>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="/theme/ol.css">
<link rel="stylesheet" type="text/css" href="/theme/site.css">
<style>
html, body, .map {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
#info {
position: absolute;
font-size: 0.7em;
top: 10px;
right: 10px;
background-color: lightgrey;
padding: 4px;
}
.geolocate-buttons {
position: absolute;
bottom: 40px;
left: 10px;
}
</style>
</head>
<body>
<div id="map" class="map"></div>
<div id="info"></div>
<img id="geolocation_marker" src="data/geolocation_marker.png" />
<div class="geolocate-buttons">
<button id="geolocate" class="btn btn-primary">Geolocate Me!</button>
<button id="simulate" class="btn btn-secondary">Simulate</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/elm-pep@1.0.6/dist/elm-pep.js"></script>
<script src="geolocation-orientation.js"></script>
<script src="common.js"></script>
</body>
</html>