POST
|
I have the report coming from an external service, which comes as a JSON object. It informs me the location where an issue is present and must be considered when calculating the route, like a cost to the lane stretch pointed by the latitude and longitude from the issue JSON object. The issues are classified as high, medium or low level. I need to set this cost to the lane stretch and then compare if it's worthwhile to select the late stretch where the issue is present or if select another route is a better decision. How open is the route solver service in order to implement this kind of cost over a route? The idea at the end is to select the lower cost route by comparing the issue level, the time travelling and the travelled distance using my own algorithm. What I've seen so far were these links below. However, I'm struggling to understand if it can be or not implemented using the Javascript API. RouteParameters-amd | API Reference | ArcGIS API for JavaScript RestrictionAttributes Property Using parameters with network attributes—Help | ArcGIS for Desktop The figures 1 and 2 illustrate what I'm looking for. The figure 1 shows the route A as the best one to be chosen, even with an issue place on it (the issue with the lowest level). The route B is the worst since it has the highest issue level/cost. The route D is possible to reach the end, but the cost would be too high to be chosen, what makes it the worst route. Figure 2 shows the route B as the best route to be chosen because the cost of the issue on B is lower than the cost that the total distance of D represents. Figure 1 Figure 2 This is the given example of restriction given by the API reference. But how it is implemented as a restriction when calculating the route? routeParams.attributeParameterValues = [{ attributeName: "Restriction", parameterName: "25 MPH", value: "2.5" }]; In summary, I need a way to insert a cost to certain routes (using the information from coming from another service) and then calculate the route considering such costs. At the end, the algorithm to be implemented will consider both issues and route distances. Thanks in advance!
... View more
08-26-2016
09:28 AM
|
0
|
0
|
505
|
POST
|
So I have the report coming from an external service, which comes as a JSON object. It informs me the location where an issue is present and must be considered when calculating the route, like a cost to the lane stretch pointed by the latitude and longitude from the issue JSON object. The issues are classified as high, medium or low level. I need to set this cost to the lane stretch and then compare if it's worthwhile to select the late stretch where the issue is present or if select another route is a better decision. How open is the route solver service in order to implement this kind of cost over a route? The idea at the end is to select the lower cost route by comparing the issue level, the time travelling and the travelled distance using my own algorithm. What I've seen so far were these links below. However, I'm struggling to understand if it can be or not implemented using the Javascript API. RouteParameters-amd | API Reference | ArcGIS API for JavaScript RestrictionAttributes Property Using parameters with network attributes—Help | ArcGIS for Desktop This is the given example of restriction. But how it is implemented as a restriction when calculating the route? routeParams.attributeParameterValues = [{
attributeName: "Restriction",
parameterName: "25 MPH",
value: "2.5"
}]; Thanks in advance!
... View more
08-24-2016
12:33 PM
|
0
|
0
|
321
|
POST
|
Is that any way to set different weight or importance of barrier on a map? I wonder if it's possible to set different kinds of barriers in terms of levels of dangerousness. For instance, if only one of two different routes can be chosen. However, both routes have 1 barrier each. On route A the barrier has level 1 of dangerousness and the barrier on route B has level 2 of dangerousness. So the route A would be chosen due to its lower dangerousness level. I'm using polygon as barriers as shown in the picture below.
... View more
08-19-2016
02:43 PM
|
0
|
2
|
1178
|
POST
|
However, this error always happens in the first attempt to solve the route. The steps that ends in error are: 1- Add Barrier 2- Add Stops 3- Solve Routes For this case, this solution is not working, giving the error below. init.js:113 TypeError: Cannot read property 'join' of undefined(…) "TypeError: Cannot read property 'join' of undefined at errorHandler (http://localhost:8080/pollutionarea.html:236:78) at .<anonymous> (https://js.arcgis.com/3.17/init.js:644:365) at c [as onError] (https://js.arcgis.com/3.17/init.js:119:79) at _errorHandler (https://js.arcgis.com/3.17/init.js:1969:40) at https://js.arcgis.com/3.17/init.js:63:209 at error (http://js.arcgis.com/3.17/esri/tasks/RouteTask.js:8:489) at https://js.arcgis.com/3.17/init.js:870:131 at c (https://js.arcgis.com/3.17/init.js:103:393) at d (https://js.arcgis.com/3.17/init.js:103:182) at reject.errback (https://js.arcgis.com/3.17/init.js:105:163)"m @ init.js:113(anonymous function) @ init.js:114filter @ init.js:71h @ init.js:114
... View more
08-11-2016
07:12 AM
|
0
|
1
|
594
|
POST
|
I need to add circles as avoid areas centred in a specific position. Instead of a point, an area must be set as a barrier. The function I'm using is addBarrier( ) as shown below: function addBarrier() {
var eventsArray = [];
routeParams.barriers = new esri.tasks.FeatureSet();
for (var i = 0; i < sensors.length; i++) {
var aircondition = JSON.stringify(sensors.contextElement.attributes[0].value);
aircondition = aircondition.toString(aircondition);
aircondition = getNumber(aircondition);
if(aircondition >= idealCondition){
var position = JSON.stringify(sensors.contextElement.attributes[1].value);
position = position.replace('"', '');
position = position.replace('"', '');
var posArray = position.split(',');
var pnt = new esri.geometry.Point(posArray[1],posArray[0],new esri.SpatialReference(6864));
routeParams.barriers.features.push(map.graphics.add(new esri.Graphic(esri.geometry.geographicToWebMercator(pnt), barrierSymbol)));
}
}
} The addBarrier() function uses the location of the objects called sensor. So instead of a simple symbol STYLE_CROSS to represent a barrier, a circle with a custom radius must be used. The circles need to be centred the same way the barriers is being placed, using the parameter position of the sensor object. The radius of each circle is defined inside each sensor. This is the JSON representation of a sensor: {
"contextElement": {
"type": "StreetFixed",
"isPattern": "false",
"id": "StreetFixed1",
"attributes": [
{
"name": "aircondition",
"type": "float",
"value": "70"
},
{
"name": "position",
"type": "geo:point",
"value": "-1.4521450790947026, -48.48221014022732"
},
{
"name": "temperature",
"type": "float",
"value": "29"
}
]
},
"statusCode": {
"code": "200",
"reasonPhrase": "OK"
}
} Here is the full code: var sensors = getSensors();
var idealCondition = 0;
dojo.require("esri.map");
dojo.require("esri.urlUtils");
dojo.require("esri.geometry.Circle");
dojo.require("esri.tasks.route");
dojo.require("esri.toolbars.draw");
dojo.require("esri.arcgis.OAuthInfo");
var map;
var urlUtils;
var Circle, routeTask, routeParams, routes = [];
var drawToolbar;
var stopSymbol, barrierSymbol, polygonBarrierSymbol, routeSymbols;
var mapOnClick_addStops_connect;
var mapOnClick_addBarriers_connect;
var Point, SpatialReference;
var Polygon;
$(document).ready(function(){
idealCondition = 0;
$('input#airqualityslider').slider({
formatter: function(value) {
idealCondition = value;
$("div#airqualityslider").html(value);
return 'Air pollution filter: ' + value;
}
}).on('slideStop', function(evt){
clearBarriers();
addBarriers();
if(routeParams.stops.features.length >= 2)
solveRoute();
});
dojo.ready(init);
$("#map").click(function(){
addStops();
});
$("#clearStopsBtn").click(function(){
clearStops();
});
$("#clearRoutesBtn").click(function(){
clearRoutes();
});
$("#clearPolygons").click(function(){
clearPolygonBarriers();
});
});
function init() {
map = new esri.Map("map", {
basemap: "streets",
// basemap: "topo-vector",
center: [-48.47, -1.436],
zoom: 13
});
function createToolBar() {
drawToolbar = new esri.toolbars.Draw(map);
drawToolbar.on("draw-end", addToMap);
}
function activateTool() {
drawToolbar.activate(esri.toolbars.Draw.CIRCLE);
}
function addToMap(evt) {
drawToolbar.markerSymbol.size = (Math.random() * (20 - 10) + 10);
drawToolbar.deactivate();
routeParams.polygonBarriers.features.push(
map.graphics.add(new esri.Graphic(evt.geometry, polygonBarrierSymbol)));
solveRoute();
}
map.on("load", createToolBar);
var node = dojo.byId("addPolygons");
dojo.connect(node, "click", activateTool);
routeTask = new esri.tasks.RouteTask("https://route.arcgis.com/arcgis/rest/services/World/Route/NAServer/Route_World");
routeParams = new esri.tasks.RouteParameters();
routeParams.stops = new esri.tasks.FeatureSet();
routeParams.barriers = new esri.tasks.FeatureSet();
routeParams.polygonBarriers = new esri.tasks.FeatureSet();
routeParams.outSpatialReference = {"wkid":102100};
dojo.connect(routeTask, "onSolveComplete", showRoute);
dojo.connect(routeTask, "onError", errorHandler);
stopSymbol = new esri.symbol.SimpleMarkerSymbol().setStyle(esri.symbol.SimpleMarkerSymbol.STYLE_CROSS).setSize(15);
stopSymbol.outline.setWidth(5);
barrierSymbol = new esri.symbol.SimpleMarkerSymbol().setStyle(esri.symbol.SimpleMarkerSymbol.STYLE_CROSS).setSize(15);
barrierSymbol.outline.setWidth(3).setColor(new dojo.Color([255,0,0]));
var polygonBarrierSymbol = new esri.symbol.SimpleFillSymbol();
routeSymbols = {
"Route 1": new esri.symbol.SimpleLineSymbol().setColor(new dojo.Color([0,0,255,0.5])).setWidth(5),
"Route 2": new esri.symbol.SimpleLineSymbol().setColor(new dojo.Color([0,255,0,0.5])).setWidth(5),
"Route 3": new esri.symbol.SimpleLineSymbol().setColor(new dojo.Color([255,0,255,0.5])).setWidth(5)
};
}
function addStops() {
removeEventHandlers();
mapOnClick_addStops_connect = dojo.connect(map, "onClick", addStop);
}
function addStop(evt) {
// routeParams.stops.features.push(map.graphics.add(new esri.Graphic(evt.mapPoint,stopSymbol,{ RouteName:dojo.byId("routeName").value })));
routeParams.stops.features.push(map.graphics.add(new esri.Graphic(evt.mapPoint,stopSymbol,{ RouteName:"Route 1" })));
if(routeParams.stops.features.length >= 1)
solveRoute();
addStops();
}
function clearStops() {
removeEventHandlers();
for (var i = routeParams.stops.features.length-1; i >= 0; i--) {
map.graphics.remove(routeParams.stops.features.splice(i, 1)[0]);
}
clearRoutes();
addStops();
}
function clearPolygonBarriers() {
removeEventHandlers();
for (var i=routeParams.polygonBarriers.features.length-1; i>=0; i--) {
map.graphics.remove(routeParams.polygonBarriers.features.splice(i, 1)[0]);
}
solveRoute();
}
function addPolygonBarrier(evt) {
drawToolbar.activate(esri.toolbars.Draw.CIRCLE);
var drawEnd_connect = dojo.connect(drawToolbar, "onDrawEnd", function(geometry) {
routeParams.polygonBarriers.features.push(
map.graphics.add(new esri.Graphic(geometry, polygonBarrierSymbol))
);
});
}
function addBarriers(){
removeEventHandlers();
addBarrier();
}
function addBarrier() {
var eventsArray = [];
routeParams.barriers = new esri.tasks.FeatureSet();
for (var i = 0; i < sensors.length; i++) {
var aircondition = JSON.stringify(sensors.contextElement.attributes[0].value);
aircondition = aircondition.toString(aircondition);
aircondition = getNumber(aircondition);
if(aircondition >= idealCondition){
var position = JSON.stringify(sensors.contextElement.attributes[1].value);
position = position.replace('"', '');
position = position.replace('"', '');
var posArray = position.split(',');
var pnt = new esri.geometry.Point(posArray[1],posArray[0],new esri.SpatialReference(6864));
routeParams.barriers.features.push(map.graphics.add(new esri.Graphic(esri.geometry.geographicToWebMercator(pnt), barrierSymbol)));
}
}
}
function clearBarriers() {
removeEventHandlers();
for (var i = routeParams.barriers.features.length - 1; i >= 0; i--) {
map.graphics.remove(routeParams.barriers.features.splice(i, 1)[0]);
}
addStops();
}
function removeEventHandlers() {
dojo.disconnect(mapOnClick_addStops_connect);
dojo.disconnect(mapOnClick_addBarriers_connect);
}
function solveRoute() {
clearBarriers();
addBarriers();
removeEventHandlers();
if(routeParams.stops.features.length > 1){
$("#map-loading").html("Solving route "+"<img src='/images/loading.gif'>");
$("#map-loading").show();
routeTask.solve(routeParams, function(){
$("#map-loading").hide();
});
}
}
function clearRoutes() {
for (var i=routes.length-1; i>=0; i--) {
map.graphics.remove(routes.splice(i, 1)[0]);
}
routes = [];
addStops();
}
function showRoute(solveResult) {
clearRoutes();
dojo.forEach(solveResult.routeResults, function(routeResult, i) {
routes.push(
map.graphics.add(
routeResult.route.setSymbol(routeSymbols[routeResult.routeName])
)
);
});
var msgs = ["Server messages:"];
dojo.forEach(solveResult.messages, function(message) {
msgs.push(message.type + " : " + message.description);
});
if (msgs.length > 1) {
alert(msgs.join("\n - "));
}
}
function errorHandler(err) {
// alert("An error occured\n" + err.message + "\n" + err.details.join("\n"));
console.log(err);
}
function getSensors() {
var result = "";
var request = new XMLHttpRequest();
if(localStorage.getItem('sensors') == null){
// `false` makes the request synchronous
request.open('GET', '../getSensors', false);
request.send(null);
if (request.status === 200) {
result = JSON.parse(request.responseText).contextResponses;
console.log("getSensors done! "+result.length+" sensors loaded.");
localStorage.setItem("sensors", JSON.stringify(result));
}
else
alert("Unknown error!");
}
else
result = JSON.parse(localStorage.getItem('sensors'));
return result;
} Thanks in advance.
... View more
08-09-2016
01:47 PM
|
0
|
3
|
1496
|
POST
|
I need to add circles as avoided areas centred in a specific position. The function I'm using is addBarrier() as shown below: function addBarrier() { var eventsArray = []; routeParams.barriers = new esri.tasks.FeatureSet(); for (var i = 0; i < sensors.length; i++) { var aircondition = JSON.stringify(sensors.contextElement.attributes[0].value); aircondition = aircondition.toString(aircondition); aircondition = getNumber(aircondition); if(aircondition >= idealCondition){ var position = JSON.stringify(sensors.contextElement.attributes[1].value); position = position.replace('"', ''); position = position.replace('"', ''); var posArray = position.split(','); var pnt = new esri.geometry.Point(posArray[1],posArray[0],new esri.SpatialReference(6864)); routeParams.barriers.features.push(map.graphics.add(new esri.Graphic(esri.geometry.geographicToWebMercator(pnt), barrierSymbol))); } } } The addBarrier() function uses the location of the objects called sensor. Instead of an X symbol to represent the location on the map, a circle with a custom radius, which will be another parameter of the object sensor
... View more
08-09-2016
01:24 PM
|
0
|
1
|
642
|
POST
|
Thank you Robert! I'll check out to see how does it work looking at the references.
... View more
06-24-2016
05:23 PM
|
0
|
0
|
458
|
POST
|
As you may suppose, I'm using a local server to request the sensors from a cloud server. The getSensor() method returns this JSON file shown below. You may create a sample of this. This is the array of sensors in JSON format: { "contextResponses": [4] 0: { "contextElement": { "type": "StreetFixed" "isPattern": "false" "id": "StreetFixed1" "attributes": [4] 0: { "name": "aircondition" "type": "float" "value": "66.1" }- 1: { "name": "lat" "type": "float" "value": "-1.4519538" }- 2: { "name": "lng" "type": "float" "value": "-48.4781193" }- 3: { "name": "temperature" "type": "float" "value": "29.8" }- - }- "statusCode": { "code": "200" "reasonPhrase": "OK" }- }- 1: { "contextElement": { "type": "StreetFixed" "isPattern": "false" "id": "StreetFixed2" "attributes": [4] 0: { "name": "aircondition" "type": "float" "value": "66.1" }- 1: { "name": "lat" "type": "float" "value": "-1.4519062" }- 2: { "name": "lng" "type": "float" "value": "-48.476207" }- 3: { "name": "temperature" "type": "float" "value": "29.8" }- - }- "statusCode": { "code": "200" "reasonPhrase": "OK" }- }- 2: { "contextElement": { "type": "StreetFixed" "isPattern": "false" "id": "StreetFixed3" "attributes": [4] 0: { "name": "aircondition" "type": "float" "value": "66.1" }- 1: { "name": "lat" "type": "float" "value": "-1.4518047" }- 2: { "name": "lng" "type": "float" "value": "-48.4744374" }- 3: { "name": "temperature" "type": "float" "value": "29.8" }- - }- "statusCode": { "code": "200" "reasonPhrase": "OK" }- }- 3: { "contextElement": { "type": "StreetFixed" "isPattern": "false" "id": "StreetFixed4" "attributes": [4] 0: { "name": "aircondition" "type": "float" "value": "66.1" }- 1: { "name": "lat" "type": "float" "value": "-1.4517104" }- 2: { "name": "lng" "type": "float" "value": "-48.4726244" }- 3: { "name": "temperature" "type": "float" "value": "29.8" }- - }- "statusCode": { "code": "200" "reasonPhrase": "OK" }- }- - } Here is the uncomment code as requested: function getSensors(){ var result; var request = new XMLHttpRequest(); request.open('GET', '/getSensors', false); if (request.status === 200) { result = JSON.parse(request.responseText).contextResponses; console.log(result); console.log("getSensors done!"); } return result; } Thanks!
... View more
06-24-2016
12:03 PM
|
0
|
1
|
458
|
POST
|
Here is the .js code used to run the service: var map;
// var sensors = getSensors();
var sensorIndex = 0;
require([
"esri/urlUtils",
"esri/config",
"esri/map",
"esri/geometry/webMercatorUtils",
"esri/graphic",
"esri/tasks/RouteTask",
"esri/tasks/RouteParameters",
"esri/tasks/FeatureSet",
"esri/symbols/SimpleMarkerSymbol",
"esri/symbols/SimpleLineSymbol",
"esri/Color",
"dojo/_base/array",
"dojo/on",
"dojo/dom",
"dijit/registry",
"dijit/layout/BorderContainer",
"dijit/layout/ContentPane",
"dijit/form/HorizontalSlider",
"dijit/form/HorizontalRuleLabels",
"dojo/domReady!"
], function (
urlUtils,
esriConfig,
Map,
webMercatorUtils,
Graphic,
RouteTask,
RouteParameters,
FeatureSet,
SimpleMarkerSymbol,
SimpleLineSymbol,
Color,
array,
on,
dom,
registry,
BorderContainer,
ContentPane,
HorizontalSlider,
HorizontalRuleLabels
) {
var map, routeTask, routeParams, routes = [];
var stopSymbol, barrierSymbol, routeSymbols;
var mapOnClick_addStops_connect, mapOnClick_addBarriers_connect;
var x = -48.48;
var y = -1.45;
map = new Map("map", {
basemap: "streets",
center: [x, y],
zoom: 15
});
routeTask = new RouteTask("https://route.arcgis.com/arcgis/rest/services/World/Route/NAServer/Route_World");
routeParams = new RouteParameters();
routeParams.stops = new FeatureSet();
routeParams.barriers = new FeatureSet();
routeParams.outSpatialReference = {"wkid":102100};
routeTask.on("solve-complete", showRoute);
routeTask.on("error", errorHandler);
stopSymbol = new SimpleMarkerSymbol().setStyle(SimpleMarkerSymbol.STYLE_CROSS).setSize(15);
stopSymbol.outline.setWidth(3);
barrierSymbol = new SimpleMarkerSymbol().setStyle(SimpleMarkerSymbol.STYLE_X).setSize(10);
barrierSymbol.outline.setWidth(3).setColor(new Color([255,0,0]));
routeSymbols = {
"Route 1": new SimpleLineSymbol().setColor(new Color([0,0,255,0.5])).setWidth(5),
"Route 2": new SimpleLineSymbol().setColor(new Color([0,255,0,0.5])).setWidth(5),
"Route 3": new SimpleLineSymbol().setColor(new Color([255,0,255,0.5])).setWidth(5)
};
//button click event listeners can't be added directly in HTML when the code is wrapped in an AMD callback
on(dom.byId("addStopsBtn"), "click", addStops);
on(dom.byId("clearStopsBtn"), "click", clearStops);
on(dom.byId("addBarriersBtn"), "click", addBarriers);
on(dom.byId("clearBarriersBtn"), "click", clearBarriers);
on(dom.byId("solveRoutesBtn"), "click", solveRoute);
on(dom.byId("clearRoutesBtn"), "click", clearRoutes);
document.getElementById("map").addEventListener("click", function(evt){
// console.log("This is a CLONE event: ");
// console.log(evt);
// console.log(" - - - - - - -");
// evt = changeParams(evt, sensorIndex);
// console.log(evt);
// console.log("\n");
// console.log("\n");
// console.log("\n");
// console.log("=======");
// addBarrier(evt);
// sensorIndex++;
// if(sensorIndex > sensors.length)
// sensorIndex = 0;
});
function addBarriers() {
removeEventHandlers();
mapOnClick_addBarriers_connect = on(map, "click", addBarrier);
}
//Adds a barrier
function addBarrier(evt) {
var eventsArray = [];
sensorArryay = getSensors();
routeParams.barriers = new FeatureSet();
for(var i = 0; i < sensorArryay.length; i++){
var obj = jQuery.extend(true, {}, evt);
eventsArray.push(obj);
}
for (var i = 0; i < sensorArryay.length; i++) {
[eventsArray.mapPoint.x,eventsArray.mapPoint.y]=webMercatorUtils.lngLatToXY(
sensorArryay.contextElement.attributes[2].value,
sensorArryay.contextElement.attributes[1].value
); routeParams.barriers.features.push(map.graphics.add(new esri.Graphic(eventsArray.mapPoint,barrierSymbol)));
}
}
//Begins listening for click events to add stops
function addStops() {
removeEventHandlers();
mapOnClick_addStops_connect = map.on("click", addStop);
}
//Clears all stops
function clearStops() {
removeEventHandlers();
for (var i=routeParams.stops.features.length-1; i>=0; i--) {
map.graphics.remove(routeParams.stops.features.splice(i, 1)[0]);
}
}
//Adds a stop. The stop is associated with the route currently displayed in the dropdown
function addStop(evt) {
routeParams.stops.features.push(
map.graphics.add(
new esri.Graphic(
evt.mapPoint,
stopSymbol,
{ RouteName:dom.byId("routeName").value }
)
)
);
}
//Clears all barriers
function clearBarriers() {
removeEventHandlers();
for (var i=routeParams.barriers.features.length-1; i>=0; i--) {
map.graphics.remove(routeParams.barriers.features.splice(i, 1)[0]);
}
}
//Stops listening for click events to add barriers and stops (if they've already been wired)
function removeEventHandlers() {
if (mapOnClick_addStops_connect) {
mapOnClick_addStops_connect.remove();
}
if (mapOnClick_addBarriers_connect) {
mapOnClick_addBarriers_connect.remove();
}
}
//Solves the routes. Any errors will trigger the errorHandler function.
function solveRoute() {
removeEventHandlers();
routeTask.solve(routeParams);
}
//Clears all routes
function clearRoutes() {
for (var i=routes.length-1; i>=0; i--) {
map.graphics.remove(routes.splice(i, 1)[0]);
}
routes = [];
}
//Draws the resulting routes on the map
function showRoute(evt) {
clearRoutes();
array.forEach(evt.result.routeResults, function(routeResult, i) {
routes.push(
map.graphics.add(
routeResult.route.setSymbol(routeSymbols[routeResult.routeName])
)
);
});
var msgs = ["Server messages:"];
array.forEach(evt.result.messages, function(message) {
msgs.push(message.type + " : " + message.description);
});
if (msgs.length > 1) {
alert(msgs.join("\n - "));
}
}
//Reports any errors that occurred during the solve
function errorHandler(err) {
alert("An error occured\n" + err.message + "\n" + err.details.join("\n"));
}
});
function getSensors(){
var result;
var request = new XMLHttpRequest();
request.open('GET', '/getSensors', false); // `false` makes the request synchronous
request.send(null);
if (request.status === 200) {
result = JSON.parse(request.responseText).contextResponses;
// console.log(result);
console.log("getSensors done!");
}
return result;
}
function eventFire(el, etype){
if (el.fireEvent) {
el.fireEvent('on' + etype);
} else {
var evObj = document.createEvent('Events');
evObj.initEvent(etype, true, false);
el.dispatchEvent(evObj);
}
}
... View more
06-23-2016
04:13 PM
|
0
|
1
|
458
|
POST
|
This is the link for the question! Thanks in advance!
... View more
06-23-2016
11:14 AM
|
0
|
6
|
2763
|
Online Status |
Offline
|
Date Last Visited |
11-11-2020
02:25 AM
|