-
Notifications
You must be signed in to change notification settings - Fork 451
/
Draggable route lines.html
385 lines (298 loc) · 15.3 KB
/
Draggable route lines.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
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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
<!DOCTYPE html>
<html lang="en">
<head>
<title>Draggable route lines - Azure Maps Web SDK Samples</title>
<meta charset="utf-8" />
<link rel="shortcut icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="This sample shows how to calculate a simple route and display it on the map using the Services module for Azure Maps." />
<meta name="keywords" content="Microsoft maps, map, gis, API, SDK, services, module, route, directions" />
<meta name="author" content="Microsoft Azure Maps" /><meta name="version" content="1.0" />
<meta name="screenshot" content="screenshot.gif" />
<!-- Add references to the Azure Maps Map control JavaScript and CSS files. -->
<link href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas.min.css" rel="stylesheet" />
<script src="https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas.min.js"></script>
<!-- Add a reference to the Azure Maps Services Module JavaScript file. -->
<script src="https://atlas.microsoft.com/sdk/javascript/service/2/atlas-service.min.js"></script>
<script>
var map, routeDS, previewDS, routeURL;
//Some intial waypoints for a route.
var waypoints = [
//Seattle
[-122.33028, 47.60323],
//Redmond
[-122.124, 47.67491]
];
var activeWaypointIdx;
var waypointLabels = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
var routePath = [];
//Flag to monitor if the mouse is down on the route line or not.
var mouseDownOnRoute = false;
//Flat to skip the addition of a mid point when a mouse event occurs on both the route line and an overlapping marker.
var skipMidPointAdd = false;
function getMap() {
//Initialize a map instance.
map = new atlas.Map('myMap', {
center: [-122.22595, 47.63437],
zoom: 11,
view: 'Auto',
//Add authentication details for connecting to Azure Maps.
authOptions: {
//Use Microsoft Entra ID authentication.
authType: 'anonymous',
clientId: 'e6b6ab59-eb5d-4d25-aa57-581135b927f0', //Your Azure Maps client id for accessing your Azure Maps account.
getToken: function (resolve, reject, map) {
//URL to your authentication service that retrieves an Microsoft Entra ID Token.
var tokenServiceUrl = 'https://samples.azuremaps.com/api/GetAzureMapsToken';
fetch(tokenServiceUrl).then(r => r.text()).then(token => resolve(token));
}
//Alternatively, use an Azure Maps key. Get an Azure Maps key at https://azure.com/maps. NOTE: The primary key should be used as the key.
//authType: 'subscriptionKey',
//subscriptionKey: '[YOUR_AZURE_MAPS_KEY]'
}
});
//Use MapControlCredential to share authentication between a map control and the service module.
var pipeline = atlas.service.MapsURL.newPipeline(new atlas.service.MapControlCredential(map));
//Construct the RouteURL object
routeURL = new atlas.service.RouteURL(pipeline);
//Wait until the map resources are ready.
map.events.add('ready', function () {
//Create two data sources, one for the calculated route, and one for the preview data that will be updated frequently.
routeDS = new atlas.source.DataSource();
map.sources.add(routeDS);
previewDS = new atlas.source.DataSource();
map.sources.add(previewDS);
//Create a layer for rendering the route line.
var routeLayer = new atlas.layer.LineLayer(routeDS, null, {
strokeColor: '#2272B9',
strokeWidth: 10,
lineJoin: 'round',
lineCap: 'round'
});
//Add a mouse down event to the route line layer so that mid-point waypoints can be added to the route.
map.events.add('mousedown', routeLayer, routeMouseDown);
//Use touch start event on map directly as it triggers faster than layer level touch events.
map.events.add('touchstart', mapTouchStart);
//When the mouse is over the route layer, change the cursor to be a pointer.
map.events.add('mouseover', routeLayer, function () {
map.getCanvasContainer().style.cursor = 'pointer';
});
//When the mouse leaves the item on the route layer, change the cursor back to the default which is grab.
map.events.add('mouseout', routeLayer, function () {
map.getCanvasContainer().style.cursor = 'grab';
});
//Create lines layers under the road labels.
map.layers.add([
routeLayer,
//Create a layer for rendering the route preview line.
new atlas.layer.LineLayer(previewDS, null, {
strokeColor: 'red',
strokeWidth: 3,
lineJoin: 'round',
lineCap: 'round'
})
], 'labels');
//Track the movement of the mouse on the map for better dragging of the route line with a new mid-point.
map.events.add('mousemove', mouseMoved);
map.events.add('touchmove', mouseMoved);
//Track when the mouse event fires to clear the mouseDownOnRoute flag.
map.events.add('mouseup', mouseUp);
map.events.add('touchend', mouseUp);
//Calculate initial directions.
calculateDirections();
});
}
function renderWaypointMarkers() {
//Create a marker for each waypoint.
var markers = [];
for (var i = 0; i < waypoints.length; i++) {
var color = 'blue';
var routeIdx = 0;
//Make the first marker green, the last marker red, and all mid-point markers, blue and smaller.
//Add the same time, calculate the nearest point in the route line to each marker and store that within the marker.
if (i === 0) {
color = 'green';
} else if (i === waypoints.length - 1) {
color = 'red';
routeIdx = routePath.length - 1;
} else {
routeIdx = getNearestRouteIdx(waypoints[i]);
}
var marker = new atlas.HtmlMarker({
color: color,
text: waypointLabels[i],
position: waypoints[i],
draggable: true
});
marker.properties = {
//Store the waypoint index so we can update it's position if it is dragged.
wpIdx: i,
//Store the nearest position index to make it easier to determine where new mid-points should be added within the waypoint array.
routeIdx: routeIdx
};
//Optionally, delete the waypoint when right clicked.
map.events.add('contextmenu', marker, markerRightClicked);
//Add mouse down event to preview bubbling of event to line layers.
map.events.add('mousedown', marker, markerMouseDown);
//Add a drag event to each marker to render a preview.
map.events.add('drag', marker, markerDragged);
//Add a drag end event to each marker to calculate the new route line.
map.events.add('dragend', marker, markerDragEnd);
//When the mouse is over the marker, change the cursor to be a pointer.
map.events.add('mouseover', marker, function () {
map.getCanvasContainer().style.cursor = 'pointer';
});
//When the mouse leaves the item on the route layer, change the cursor back to the default which is grab.
map.events.add('mouseout', marker, function () {
map.getCanvasContainer().style.cursor = 'grab';
});
markers.push(marker);
}
//Remove an markers already on the map.
var oldMarkers = map.markers.getMarkers();
map.markers.remove(oldMarkers);
//Add the new markers to the map.
map.markers.add(markers);
}
function markerMouseDown(e) {
//Disable event bubbling so that if marker overlaps line, a new mid-point isn't also added.
skipMidPointAdd = true;
//Track the active waypoint index.
activeWaypointIdx = e.target.properties.wpIdx;
}
function markerDragged(e) {
//NOTE: The next two lines below are the same as markerMouseDown event, but needed here for touch support.
//Disable event bubbling so that if marker overlaps line, a new mid-point isn't also added.
skipMidPointAdd = true;
//Track the active waypoint index.
activeWaypointIdx = e.target.properties.wpIdx;
//Update the position of the corresponding waypoint.
waypoints[activeWaypointIdx] = e.target.getOptions().position;
//Update preview.
updatePreview();
}
function markerDragEnd(e) {
//Update the position of the corresponding waypoint.
waypoints[activeWaypointIdx] = e.target.getOptions().position;
skipMidPointAdd = false;
activeWaypointIdx = null;
//Calculate new route directions.
calculateDirections();
}
function markerRightClicked(e) {
if (waypoints.length > 2) {
//Remove the waypoint/marker.
waypoints.splice(e.target.properties.wpIdx, 1);
map.markers.remove(e.target);
skipMidPointAdd = true;
//Recalculate directions.
calculateDirections();
}
}
function routeMouseDown(e) {
if (!skipMidPointAdd) {
//Calculate the nearest position index in the route line to the mouse position.
var routeIdx = getNearestRouteIdx(e.position);
//Determine where in the order of waypoints a new mid-point should be added.
var markers = map.markers.getMarkers();
//Don't want to compare to the last marker (end of the line) as new points should be added before it.
for (var i = 0; i < markers.length - 1; i++) {
if (routeIdx > markers[i].properties.routeIdx) {
activeWaypointIdx = i + 1;
}
}
if (activeWaypointIdx) {
waypoints.splice(activeWaypointIdx, 0, e.position);
}
//Enable the mouseDownOnRoute flag.
mouseDownOnRoute = true;
//Disable the maps dragging capabilities.
map.setUserInteraction({ dragPanInteraction: false });
}
}
function mouseMoved(e) {
//If the mouse down event fired on the route line and dragged, update the preview waypoint.
if (mouseDownOnRoute) {
waypoints[activeWaypointIdx] = e.position;
//Update preview.
updatePreview();
}
}
function mouseUp() {
//If new mid-point added to line, calculate new directions.
if (mouseDownOnRoute) {
calculateDirections();
}
//Clear the mouseDownOnRoute flag.
mouseDownOnRoute = false;
activeWaypointIdx = null;
skipMidPointAdd = false;
//Enable the maps dragging capabilities.
map.setUserInteraction({ dragPanInteraction: true });
}
function updatePreview() {
//Create a preview line between the waypoints on either side of the active point.
var wp = [];
if (activeWaypointIdx > 0) {
wp.push(waypoints[activeWaypointIdx - 1]);
}
wp.push(waypoints[activeWaypointIdx]);
if (activeWaypointIdx < waypoints.length - 1) {
wp.push(waypoints[activeWaypointIdx + 1]);
}
previewDS.setShapes([
new atlas.data.LineString(wp)
]);
}
function calculateDirections() {
//Calculate a route.
routeURL.calculateRouteDirections(atlas.service.Aborter.timeout(10000), waypoints).then((directions) => {
//Clear the preview lines.
previewDS.clear();
//Get the route data as GeoJSON and add it to the route data source.
var data = directions.geojson.getFeatures();
routeDS.setShapes(data);
//Extract the route path points for other calculations.
routePath = [];
data.features.forEach(f => {
f.geometry.coordinates.forEach(c => {
routePath = routePath.concat(c);
});
});
//Redraw the markers.
renderWaypointMarkers();
});
}
function getNearestRouteIdx(position) {
var routeIdx = 0;
var maxDistance = Infinity;
for (var i = 0; i < routePath.length; i++) {
var d = atlas.math.getDistanceTo(position, routePath[i]);
if (d < maxDistance) {
routeIdx = i;
maxDistance = d;
}
}
return routeIdx;
}
function mapTouchStart(e) {
//Check that if a route path exists.
//Check to see if touch event occured on the route line.
if (routePath && e.shapes && e.shapes.length > 0 && e.shapes[0] && e.shapes[0] instanceof atlas.Shape && routeDS.getShapeById(e.shapes[0].getId())) {
//Now that we know the touch event happended on a route line, call the route mouse down event.
routeMouseDown(e);
}
}
</script>
</head>
<body onload="getMap()">
<div id="myMap" style="position:relative;width:100%;min-width:290px;height:600px;"></div>
<fieldset style="width:calc(100% - 30px);min-width:290px;margin-top:10px;">
<legend>Draggable route lines</legend>
This sample shows how to make a route line draggable.
This sample uses two data sources, one for rendering the route line, and one for rendering a out preview which is updated frequently on mouse move.
HTML Markers are used for points as these are easy to drag around on the map.
An optional feature in this example deletes a waypoint when its right clicked.
</fieldset>
</body>
</html>