-
Notifications
You must be signed in to change notification settings - Fork 442
/
Basic snap to road logic.html
254 lines (211 loc) · 11.1 KB
/
Basic snap to road logic.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
<!DOCTYPE html>
<html lang="en">
<head>
<title>Basic snap to road logic - 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 snap individual points to the rendered roads on the map." />
<meta name="keywords" content="Microsoft maps, map, gis, API, SDK, snap to road, snap to roads, snapping, road network, GPS" />
<meta name="author" content="Microsoft Azure Maps" /><meta name="version" content="1.0" />
<meta name="screenshot" content="screenshot.jpg" />
<!-- 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>
<script>
var map, datasource, popup, origins = [];
//Name of all road source layers in the maps vector tiles.
var roadLayers = [
//Azure Maps v3 Web SDK (bing-mvt)
'road',
//Azure Maps v2 Web SDK styles (vectorTiles)
'Connecting road',
'Connecting road tunnel',
'International road',
'International road tunnel',
'Local road',
'Local road tunnel',
'Major local road',
'Major local road tunnel',
'Major road',
'Major road tunnel',
'Minor local road',
'Minor local road tunnel',
'Motorway',
'Motorway tunnel',
'Secondary road',
'Secondary road tunnel',
'Toll connecting road',
'Toll connecting road tunnel',
'Toll international road',
'Toll international road tunnel',
'Toll local road',
'Toll local road tunnel',
'Toll major local road',
'Toll major local road tunnel',
'Toll major road',
'Toll major road tunnel',
'Toll minor local road',
'Toll minor local road tunnel',
'Toll motorway',
'Toll motorway tunnel',
'Toll secondary road',
'Toll secondary road tunnel',
//Parking lot roads. Comment this out if not desired.
'Parking road'
];
//The max distance a coordinate needs to be from a road in meters if it is to snap to it.
var maxSnappingDistance = 150;
//The min distance in meters the coordinate needs to be away from a road from a past snapping calculation, before it is snapped again.
var minSnappingDistance = 2;
function getMap() {
//Initialize a map instance.
map = new atlas.Map('myMap', {
center: [-122.33, 47.6],
zoom: 15,
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]'
}
});
//Wait until the map resources are ready.
map.events.add('load', function () {
//Create a data source to add your data to.
datasource = new atlas.source.DataSource();
map.sources.add(datasource);
//Add a layer for rendering data.
var layer = new atlas.layer.BubbleLayer(datasource, null, {
color: [
'match',
['get', 'type'],
'snapped', 'red',
'blue'
]
});
map.layers.add(layer);
//Create a popup but leave it closed so we can update it and display it later.
popup = new atlas.Popup();
//Add a click event to the layer.
map.events.add('click', layer, showPopup);
//Load an initial set of random points.
generateRandomPoints();
//Run snapping logic as the map moves.
map.events.add('moveend', snapPoints);
});
//Wait until the map has loaded as we will need the map to be rendered first before we try snapping.
map.events.add('load', function () {
snapPoints();
});
}
function generateRandomPoints() {
origins = [];
var c = map.getCamera().center;
//Generate 10 random points near the center of the map.
for (var i = 0; i < 10; i++) {
var coord = [
c[0] + (Math.random() * 0.01 - 0.005),
c[1] + (Math.random() * 0.01 - 0.005)
];
//Store the source coordinate as a property of the point feature so that we can update the points coordinate to the snapped coordinate without losing the original coordinate information.
origins.push(new atlas.data.Feature(new atlas.data.Point(coord), {
type: 'origin'
}));
}
//Overwrite all shapes in the datasource.
datasource.setShapes(origins);
snapPoints();
}
function snapPoints() {
var snappedPoints = [];
var lines;
//Loop through each feature and calculate the distance to the closest road.
for (var j = 0; j < origins.length; j++) {
var point = origins[j];
//Only retrieve the line data from the map if a point feature needs to be snapped. This is a simple optimization.
if (!lines) {
//Retrieve all rendered line data from the map.
lines = map.layers.getRenderedShapes(null, null, ['any', ['==', ['geometry-type'], 'LineString'], ['==', ['geometry-type'], 'MultiLineString']]);
}
var minDistance = Number.MAX_VALUE;
var closestCoord = point.geometry.coordinates;
//Snap to the closest road.
for (var i = 0, len = lines.length; i < len; i++) {
//Ensure the layer has a sourceLayer (indicates its from a vector tile source) and that the source layer is one of the maps base map layers.
if (lines[i].sourceLayer && roadLayers.indexOf(lines[i].sourceLayer) !== -1) {
//Get the closest point on the source layer to the original source coordinate of the point feature.
var cp = atlas.math.getClosestPointOnGeometry(point.geometry.coordinates, lines[i]);
//Ensure that the closest point is closer than the previously calculated snapped coordinates for the point.
if (cp && cp.properties.distance <= maxSnappingDistance && cp.properties.distance < minDistance) {
//Capture the distance and closest coordinate.
minDistance = cp.properties.distance;
closestCoord = cp.geometry.coordinates;
}
}
}
snappedPoints.push(new atlas.data.Feature(new atlas.data.Point(closestCoord), {
type: 'snapped',
distance: minDistance
}));
}
//Overwrite all features in the datasource with the snapped features.
datasource.setShapes(origins);
datasource.add(snappedPoints);
}
function showPopup(e) {
if (e.shapes && e.shapes.length > 0) {
var properties = e.shapes[0].getProperties();
var content;
if(properties.type == 'snapped'){
content = `<div style="padding:10px">Type: snapped<br/>Distance: ${properties.distance}m</div>`;
} else {
content = `<div style="padding:10px">Type: origin</div>`;
}
popup.setOptions({
//Update the content of the popup.
content: content,
//Update the position of the popup with the point features coordinate.
position: e.shapes[0].getCoordinates()
});
//Open the popup.
popup.open(map);
}
}
</script>
</head>
<body onload="getMap()">
<div id="myMap" style="position:relative;width:100%;min-width:290px;height:600px;"></div>
<div style="position:absolute;top:10px;left:10px;background-color:white;padding:10px;border-radius:10px;">
<table>
<tr>
<td><svg viewBox="0 0 20 5" width="20px" height="5px" xmlns="http://www.w3.org/2000/svg"><line x1="5" y1="2" x2="15" y2="2" stroke="Blue" /></svg></td>
<td>Origin points</td>
</tr>
<tr>
<td><svg viewBox="0 0 20 5" width="20px" height="5px" xmlns="http://www.w3.org/2000/svg"><line x1="5" y1="2" x2="15" y2="2" stroke="Red" /></svg></td>
<td>Snapped points</td>
</tr>
</table>
<input type="button" value="Randomize points" onclick="generateRandomPoints()" />
</div>
<fieldset style="width:calc(100% - 30px);min-width:290px;margin-top:10px;">
<legend>Basic snap to road logic</legend>
This sample shows how to snap individual points to the rendered roads on the map. Click on any point to find out how far it moved in order to snap to the nearest road.
This sample calculates the nearest rendered road every time the map moves within certain distance thresholds since only roads in view and at certain zoom levels are rendered.
This is a cheap way to snap real-time GPS coordinates to the road network on the map.
Similar logic can be used for snapping to other line featurs on the map.
Simply change the names in the source layer to the layers you want to snap to.
A full list of source layers available in the base road map vector tiles is documented <a href="https://developer.tomtom.com/maps-api/maps-api-documentation-vector/tile">here</a>
</fieldset>
</body>
</html>