Considering custom Feature Layers preferring, restricting or unrestricting roads
Custom Feature Layers allow to set preference levels when computing a route A route corresponds to a path of a vehicle through the underlying transport network. The main attributes of a route are the distance and the time that the vehicle travels along the path., for instance to prefer or to avoid some roads. The process is done in 2 steps. At first, custom Feature Layer is created to assign custom road class to the segments. Then, it is also possible to choose per request the preference level to apply for each custom road class. Moreover, note that polygons are used to get segmentIds with the getSegments() operations in the following examples.
Preferring some roads
Sometimes a road should be preferred for any reasons. Preferring a route consists in favoring the passage by a route even it is not the fastest or the shortest. Thus, the access from one side of a city can be preferred without being mandatory for examples, due to best maintained roads, less traffic lights or simply because this way is more scenic. This first example shows how to mark, using the customRoadClass property, the roads that must be preferred during the route calculation.
var A = { "$type": "OffRoadWaypoint", "location": { "offRoadCoordinate": { "x": 6.211525, "y": 49.633156 } } }; var B = { "$type": "OffRoadWaypoint", "location": { "offRoadCoordinate": { "x": 6.134210, "y": 49.600691 } } }; var PolygonSelector = [ {x: 6.136742470430768, y: 49.60031401651007}, {x: 6.137574109920241, y: 49.59845436559709}, {x: 6.141651826126618, y: 49.59856733706834}, {x: 6.142604187477479, y: 49.59894970010595}, {x: 6.143623616529054, y: 49.59995773374315}, {x: 6.144938143464037, y: 49.60061815827587}, {x: 6.1456881165504305, y: 49.601679270835035}, {x: 6.145433259287538, y: 49.60224408821845}, {x: 6.1449369583019084, y: 49.603443217280045}, {x: 6.146345380017922, y: 49.60409490548781}, {x: 6.148665922464322, y: 49.6039471902575}, {x: 6.149912228972396, y: 49.60416496462745}, {x: 6.153131478609023, y: 49.60572897853925}, {x: 6.155036201310704, y: 49.606849824312754}, {x: 6.1570875672407075, y: 49.607510987509734}, {x: 6.160897012644032, y: 49.60888375263598}, {x: 6.164450201045619, y: 49.609344785253754}, {x: 6.167079254915544, y: 49.61000508264187}, {x: 6.167924307945176, y: 49.60988344958452}, {x: 6.168635225573263, y: 49.61042210796284}, {x: 6.171531387539791, y: 49.611638913143594}, {x: 6.173892170606666, y: 49.6135241235297}, {x: 6.174375058052137, y: 49.614305986629354}, {x: 6.174804291337051, y: 49.61477509846869}, {x: 6.174670155935505, y: 49.615009652695115}, {x: 6.173623899803615, y: 49.61498359117013}, {x: 6.174079960168807, y: 49.614375484964555}, {x: 6.173449523781632, y: 49.61380212073545}, {x: 6.171276530276898, y: 49.611821356045176}, {x: 6.168499927465306, y: 49.61062243304813}, {x: 6.167802423377379, y: 49.61010115298176}, {x: 6.166997610968222, y: 49.61017065731269}, {x: 6.164395384178612, y: 49.60955380291236}, {x: 6.160748805001712, y: 49.60904892676554}, {x: 6.156912532518071, y: 49.60771092019736}, {x: 6.154793193173971, y: 49.606981082953844}, {x: 6.152917091513786, y: 49.60588553172315}, {x: 6.149778323118072, y: 49.60436496820247}, {x: 6.1486113451247935, y: 49.60413905186159}, {x: 6.146318256023311, y: 49.60423436944379}, {x: 6.14556709777479, y: 49.60412141110161}, {x: 6.1446952176648395, y: 49.60373908861704}, {x: 6.144561082263333, y: 49.60324380275899}, {x: 6.145312240511857, y: 49.60171446730511}, {x: 6.144829353066386, y: 49.600836815638026}, {x: 6.143408645585527, y: 49.60007944661858}, {x: 6.142496524855141, y: 49.59907141549742}, {x: 6.141597817664915, y: 49.598758574015946}, {x: 6.137989575363851, y: 49.59866298316296}, {x: 6.137265244195645, y: 49.600305381769765} ] var outputString = ""; var map = new L.Map('map', { center: [49.627371, 6.146428], zoom: 13 }); // Add tile layer to map var tiles = new L.tileLayer.xserver(xServerUrl + '/services/rest/XMap/experimental/tile/{z}/{x}/{y}' + '?layers=background,transport,labels' + '&contentType=JSON', { pane: "overlayPane", maxZoom: 18, }).addTo(map); // Display the polygon var latlngs = []; for (var polygonIdx = 0; polygonIdx < PolygonSelector.length; ++polygonIdx) { latlngs.push(L.latLng(PolygonSelector[polygonIdx].y, PolygonSelector[polygonIdx].x)); } L.polygon(latlngs, {color: '#000'}).addTo(map); //----- Route without custom Feature Layer ----- function calculateSpecificRouteWithoutCFL() { xroute.calculateRoute({ "waypoints": [A, B], "resultFields": { "polyline": true }, "geometryOptions": { "responseGeometryTypes": ["GEOJSON"] } }, function(route, exception) { var geoJson = route.polyline.geoJSON; displayGeoJson(geoJson, '#2882C8', '5,10', false); outputString += 'Route without custom Feature Layer= ' + route.distance + ' m in ' + route.travelTime + ' s. '; print(outputString); }); } function displayGeoJsonForRouteWitoutCFL(geoJson, color) { var jsonObject = JSON.parse(geoJson); var geoJsonLayer = new L.GeoJSON(jsonObject, { style: { color: color, weight: 6, dashArray: '5,10' } }).addTo(map); map.fitBounds(geoJsonLayer.getBounds()); }; //----- Find segments surrounded ----- function getSegmentsToPrefer(searchCoords) { var foundSegmentIds = new Array (); xdata.getSegments({ "$type": "SegmentsBySurroundingPolygonRequest", "resultFields": { "polyline": true, "descriptors": true }, "polygon": { "plain": { "$type": "Polygon", "polygonRings": [{ "polyline": searchCoords }] } }, "geometryOptions": { "responseGeometryTypes": ["GEOJSON"] } }, function(result, exception) { var geoJson; for (let i = 0; i < result.segments.length; i++) { geoJson = result.segments[i].polyline.geoJSON; displayGeoJson(geoJson, '#481111', '', false); foundSegmentIds.push(result.segments[i].id); } } ); return foundSegmentIds; } //----- Create FeatureLayer ----- // The property used is "customRoadClass" with the class name "CUSTOM_ROAD_CLASS_1" function createCustomFeatureLayer(segmentIds) { var createdFeatureLayer; xdata.createFeatureLayer({ "themeId" : "PTV_RoadAttributes", "features" : [ { "segmentIds" : segmentIds , "descriptions" : [ { "attributes" : [ { "key" : "customRoadClass", "value" : "CUSTOM_ROAD_CLASS_1" } ] } ] } ] }, function(result, exception) { createdFeatureLayer = result.binaryFeatureLayer; }); return createdFeatureLayer; }; //----- Route with custom Feature Layer ----- // For the routing, the field "preferredRouteTypes" from the VehicleProfile is used with the same value ("CUSTOM_ROAD_CLASS_1") used for the layer creation. This allows to potentially prefer this segments. function calculateSpecificRouteWithCFL(binaryFeatureLayer) { xroute.calculateRoute({ "waypoints": [A, B], "routeOptions": { "binaryFeatureLayer": binaryFeatureLayer }, "requestProfile": { "vehicleProfile": { "preferredRouteTypes": "CUSTOM_ROAD_CLASS_1" } }, "resultFields": { "polyline": true }, "geometryOptions": { "responseGeometryTypes": ["GEOJSON"] } }, function(route, exception) { var geoJson = route.polyline.geoJSON; displayGeoJson(geoJson, '#2882C8', '', true); outputString += 'Route with custom Feature Layer = ' + route.distance + ' m in ' + route.travelTime + ' s. '; print(outputString); }); } function displayGeoJson(geoJson, color, dashArraySettings, fitBounds) { var jsonObject = JSON.parse(geoJson); var geoJsonLayer = new L.GeoJSON(jsonObject, { style: { color: color, weight: 8, dashArray : dashArraySettings } }).addTo(map); if (fitBounds) { map.fitBounds(geoJsonLayer.getBounds()); } }; var initialized = false; tiles.on('load', function () { if (!initialized) { calculateSpecificRouteWithoutCFL(); var segmentIds = getSegmentsToPrefer(PolygonSelector); var newBinaryFeatureLayer = createCustomFeatureLayer(segmentIds); calculateSpecificRouteWithCFL(newBinaryFeatureLayer); initialized = true; } });The dashed blue route is the optimal one, the blue route is the prefer one.
Avoiding some roads
The documentation of the vehicle profile list all the other possibilities to use a custom road class. This second example shows how to mark, using the customRoadClass property, the roads that must be restricted (i.e. avoided) during the route calculation.
var A = { "$type": "OffRoadWaypoint", "location": { "offRoadCoordinate": { "x": 6.120951175689697, "y": 49.60680273231251 } } }; var B = { "$type": "OffRoadWaypoint", "location": { "offRoadCoordinate": { "x": 6.125956177711488, "y": 49.61973633235437 } } }; var PolygonSelector = [ {x: 6.1214017868042, y: 49.60692787898863}, {x: 6.1204683780670175, y: 49.6097157855301}, {x: 6.119824647903443, y: 49.611085347047734}, {x: 6.120017766952515, y: 49.611891771362565}, {x: 6.1206185817718515, y: 49.612948445097146}, {x: 6.121906042099, y: 49.61510343236959}, {x: 6.122066974639893, y: 49.61541624518304}, {x: 6.122721433639526, y: 49.615607407580626}, {x: 6.123096942901612, y: 49.615762074789934}, {x: 6.123869419097901, y: 49.61608531029421}, {x: 6.124244928359985, y: 49.61616872556017}, {x: 6.125476062297822, y: 49.616651836167655}, {x: 6.125883758068085, y: 49.61679259770987}, {x: 6.126181483268739, y: 49.61687774955655}, {x: 6.126229763031006, y: 49.6168273535836}, {x: 6.124258339405061, y: 49.61608531029421}, {x: 6.122260093688965, y: 49.615200752126704}, {x: 6.122045516967773, y: 49.614477800719285}, {x: 6.120876073837281, y: 49.61281636213308}, {x: 6.120210886001587, y: 49.61155112823647}, {x: 6.120758056640625, y: 49.609687976775824}, {x: 6.121873855590821, y: 49.6072755069589} ]; var outputString = ""; var map = new L.Map('map', { center: [49.627371, 6.146428], zoom: 13 }); // Add tile layer to map var tiles = new L.tileLayer.xserver(xServerUrl + '/services/rest/XMap/experimental/tile/{z}/{x}/{y}' + '?layers=background,transport,labels' + '&contentType=JSON', { pane: "overlayPane", maxZoom: 18, }).addTo(map); // Display the polygon var latlngs = []; for (var polygonIdx = 0; polygonIdx < PolygonSelector.length; ++polygonIdx) { latlngs.push(L.latLng(PolygonSelector[polygonIdx].y, PolygonSelector[polygonIdx].x)); } L.polygon(latlngs, {color: '#000'}).addTo(map); //----- Route without custom Feature Layer ----- // For the routing, the field "restrictedRouteTypes" from the VehicleProfile is used with the same value ("CUSTOM_ROAD_CLASS_1") used for the layer creation. This allows to potentially avoid this segments. function calculateSpecificRouteWithoutCFL() { xroute.calculateRoute({ "waypoints": [A, B], "resultFields": { "polyline": true }, "geometryOptions": { "responseGeometryTypes": ["GEOJSON"] } }, function(route, exception) { var geoJson = route.polyline.geoJSON; displayGeoJson(geoJson, '#2882C8', '5,10', false); outputString += 'Route without custom Feature Layer = ' + route.distance + ' m in ' + route.travelTime + ' s. '; print(outputString); }); }; //----- Find segments surrounded ----- function getSegmentsToAvoid(searchCoords) { var foundSegmentIds = new Array (); xdata.getSegments({ "$type": "SegmentsBySurroundingPolygonRequest", "resultFields": { "polyline": true, "descriptors": true }, "polygon": { "plain": { "$type": "Polygon", "polygonRings": [{ "polyline": searchCoords }] } }, "geometryOptions": { "responseGeometryTypes": ["GEOJSON"] } }, function(result, exception) { var geoJson; for (let i = 0; i < result.segments.length; i++) { geoJson = result.segments[i].polyline.geoJSON; displayGeoJson(geoJson, '#481111', '' , false); foundSegmentIds.push(result.segments[i].id); } } ); return foundSegmentIds; } //----- Create FeatureLayer ----- // The property used is "customRoadClass" with the class name "CUSTOM_ROAD_CLASS_1" function createCustomFeatureLayer(segmentIds) { var createdFeatureLayer; xdata.createFeatureLayer({ "themeId" : "PTV_RoadAttributes", "features" : [ { "segmentIds" : segmentIds , "descriptions" : [ { "attributes" : [ { "key" : "customRoadClass", "value" : "CUSTOM_ROAD_CLASS_1" } ] } ] } ] }, function(result, exception) { createdFeatureLayer = result.binaryFeatureLayer; }); return createdFeatureLayer; }; //----- Route with custom Feature Layer ----- // For the routing, the field "restrictedRouteTypes" from the VehicleProfile is used with the same value ("CUSTOM_ROAD_CLASS_1") used for the layer creation. This allows to potentially avoid this segments. function calculateSpecificRouteWithCFL(binaryFeatureLayer) { xroute.calculateRoute({ "waypoints": [A, B], "routeOptions": { "binaryFeatureLayer": binaryFeatureLayer }, "requestProfile": { "vehicleProfile": { "restrictedRouteTypes": "CUSTOM_ROAD_CLASS_1" } }, "resultFields": { "polyline": true }, "geometryOptions": { "responseGeometryTypes": ["GEOJSON"] } }, function(route, exception) { var geoJson = route.polyline.geoJSON; displayGeoJson(geoJson, '#2882C8', '' , true); outputString += 'Route with custom Feature Layer= ' + route.distance + ' m in ' + route.travelTime + ' s. '; print(outputString); }); } function displayGeoJson(geoJson, color, dashArraySettings, fitBounds) { var jsonObject = JSON.parse(geoJson); var geoJsonLayer = new L.GeoJSON(jsonObject, { style: { color: color, weight: 8, dashArray : dashArraySettings } }).addTo(map); if (fitBounds) { map.fitBounds(geoJsonLayer.getBounds()); } }; var initialized = false; tiles.on('load', function () { if (!initialized) { var segmentIds = getSegmentsToAvoid(PolygonSelector); var newBinaryFeatureLayer = createCustomFeatureLayer(segmentIds); calculateSpecificRouteWithCFL(newBinaryFeatureLayer); calculateSpecificRouteWithoutCFL(); initialized = true; } });The dashed blue route is the shortest, but the routing restricts the access due to the using of the custom road class as 'restrictedRouteTypes'. So another route (blue) is taken.
Ignoring TruckAttributes penalties within an area
Users have the possibility to reset the penalties inherited from other Feature Layer themes (like PTV_TruckAttributes or PTV_PreferredRoutes), for certain segments.
var A = { "$type": "OffRoadWaypoint", "location": { "offRoadCoordinate": { "x": 6.145634652057198, "y": 49.62472633454166 } } }; var B = { "$type": "OffRoadWaypoint", "location": { "offRoadCoordinate": { "x": 6.13909852507375, "y": 49.62581980293431 } } }; var PolygonSelector = [ {x: 6.137917246764941, y: 49.62557541868346}, {x: 6.138346548182235, y: 49.62517376382938}, {x: 6.1433312154392326, y: 49.627042974006606}, {x: 6.145286921313478, y: 49.62423140772966}, {x: 6.145883173136394, y: 49.62447858479398}, {x: 6.143569715644339, y: 49.62766087907956} ]; var outputString = ""; var map = new L.Map('map', { center: [49.627371, 6.146428], zoom: 13 }); // Add tile layer to map var tiles = new L.tileLayer.xserver(xServerUrl + '/services/rest/XMap/experimental/tile/{z}/{x}/{y}' + '?layers=background,transport,labels,PTV_TruckAttributes' + '&contentType=JSON', { pane: "overlayPane", maxZoom: 15, }).addTo(map); // Display the polygon var latlngs = []; for (var polygonIdx = 0; polygonIdx < PolygonSelector.length; ++polygonIdx) { latlngs.push(L.latLng(PolygonSelector[polygonIdx].y, PolygonSelector[polygonIdx].x)); } L.polygon(latlngs, {color: '#000'}).addTo(map); //----- Find segments surrounded ----- function getSegmentsToDisregard(searchCoords) { var foundSegmentIds = new Array (); xdata.getSegments({ "$type": "SegmentsBySurroundingPolygonRequest", "resultFields": { "polyline": true, "descriptors": true }, "polygon": { "plain": { "$type": "Polygon", "polygonRings": [{ "polyline": searchCoords }] } }, "geometryOptions": { "responseGeometryTypes": ["GEOJSON"] } }, function(result, exception) { var geoJson; for (let i = 0; i < result.segments.length; i++) { geoJson = result.segments[i].polyline.geoJSON; displayGeoJson(geoJson, '#481111', '', false); foundSegmentIds.push(result.segments[i].id); } } ); return foundSegmentIds; } //----- Create FeatureLayer ----- // The property used is "customRoadClass" with the class name "CUSTOM_ROAD_CLASS_1" function createCustomFeatureLayer(segmentIds) { var createdFeatureLayer; xdata.createFeatureLayer({ "themeId" : "PTV_RoadAttributes", "features" : [ { "segmentIds" : segmentIds , "descriptions" : [ { "attributes" : [ { "key" : "customRoadClass", "value" : "CUSTOM_ROAD_CLASS_1" } ] } ] } ] }, function(result, exception) { createdFeatureLayer = result.binaryFeatureLayer; }); return createdFeatureLayer; }; //----- Route ----- // For the routing, the field "unrestrictedRouteTypes" from the VehicleProfile is used with the same value ("CUSTOM_ROAD_CLASS_1") used for the layer creation. This allows to consider segments without restrictions from others Feature Layers or profile settings. function calculateSpecificRouteWithCFL(binaryFeatureLayer) { xroute.calculateRoute({ "waypoints": [A, B], "routeOptions": { "binaryFeatureLayer": binaryFeatureLayer }, "requestProfile": { "vehicleProfile": { "unrestrictedRouteTypes": "CUSTOM_ROAD_CLASS_1" }, "featureLayerProfile": { "themes": [{ "id": "PTV_TruckAttributes", "enabled": "true" }] } }, "resultFields": { "polyline": true }, "geometryOptions": { "responseGeometryTypes": ["GEOJSON"] } }, function(route, exception) { var geoJson = route.polyline.geoJSON; displayGeoJson(geoJson, '#2882C8', '', true); outputString += 'Route with PTV_TruckAttributes = ' + route.distance + ' m in ' + route.travelTime + ' s. '; print(outputString); }); } function calculateSpecificRouteWithoutCFL() { xroute.calculateRoute({ "waypoints": [A, B], "requestProfile": { "featureLayerProfile": { "themes": [{ "id": "PTV_TruckAttributes", "enabled": "true" }] } }, "resultFields": { "polyline": true }, "geometryOptions": { "responseGeometryTypes": ["GEOJSON"] } }, function(route, exception) { var geoJson = route.polyline.geoJSON; displayGeoJson(geoJson, '#2882C8', '5,10', false ); outputString += 'Route ignoring the PTV_TruckAttributes = ' + route.distance + ' m in ' + route.travelTime + ' s. '; print(outputString); }); } function displayGeoJson(geoJson, color, dashArraySettings, fitBounds) { var jsonObject = JSON.parse(geoJson); var geoJsonLayer = new L.GeoJSON(jsonObject, { style: { color: color, weight: 8, dashArray : dashArraySettings } }).addTo(map); if (fitBounds) { map.fitBounds(geoJsonLayer.getBounds()); } }; var initialized = false; tiles.on('load', function () { if (!initialized) { calculateSpecificRouteWithoutCFL(); var segmentIds = getSegmentsToDisregard(PolygonSelector); var newBinaryFeatureLayer = createCustomFeatureLayer(segmentIds); calculateSpecificRouteWithCFL(newBinaryFeatureLayer); initialized = true; } });A first route is computed (dashed blue line). This route does not take the shortest path. It makes a detour because there is a weight restriction on the optimal route (restriction from PTV_TruckAttributes layer). Then, a custom Feature Layer is created to locally override this weight restriction. As a result, the route (blue) takes the shortest path in this case.