I'm kinda new to javascript and I ran into an issue on the website I'm building. A little context: I have a leaflet map, on that map there are multiple vehicles. When I put a new marker on the map, the system has to calculate all routes between that marker and the 30ish vehicles I already have. Then it has to take that array of routes and sort it by shortest distance.
I took a brief look at promises, but never managed to get it to work properly.
var get_all_routes = function(){
var promise = new Promise(function(resolve,reject){
var rutas = [];
for(var i = 0;i<Object.keys(puntos).length;i++){
get_route(ambulance[i]).then(function(response){
rutas.push(response);
});
}
resolve(rutas);
});
return promise;
}
ambulance is a global array with the location of every vehicle on the map.
function get_route(punto){
return new Promise(function(resolve,reject){
var waypoints = [
L.Routing.waypoint(L.latLng(feature.getLatLng().lat, feature.getLatLng().lng)),
L.Routing.waypoint(L.latLng(punto.getLatLng().lat,punto.getLatLng().lng)),
];
var router = new L.routing.osrmv1({});
router.route(waypoints, function(error, routes) {
if(error==null){
resolve(routes[0]);
}else
resolve('No route available');
}, null, {});
});
}
feature is the newly marker placed on the map.
var sort_cars = function(rutas) {
var promise = new Promise(function(resolve,reject){
rutas.sort(function(a, b){return a.summary.totalDistance-
b.summary.totalDistance});
resolve(rutas);
});
return promise;
}
Sorting based on total distance.
And this is how I call this:
get_all_routes().then(function(resolve){
window.setTimeout(function() {sort_cars(resolve).then(function(resolve){
routes = resolve;
other_func()
.then(other_func2(routes));
})}, 3000);
});
other_func() and other_func2() do non important stuff. This is working, until it doesn't. Because of the timeout of 3s, if the routing agent takes more than that to get all the routes then all hell breaks loose.
What I need is for the sort_cars() function to wait until all routes are ready in order to do it's thing. Please help!
I ended up doing some more research, and found this tutorial:
http://jingding.blogspot.cl/2012/05/jquery-deferred-objects-promise.html
And changed my code to:
var get_all_routes2=function(){
var rutas = [];
mymap.spin(true);
mymap._handlers.forEach(function(handler) {
handler.disable();
});
var currentStep = get_route2(ambulancias[0].id_recurso,ambulancias[0],puntos[0].tipos_estados.alias2,puntos[0].disponible,puntos[0].servicio,0,rutas);
for(var i = 1;i<Object.keys(puntos).length;i++){
currentStep = currentStep.then(function(j){
return get_route2(ambulancias[j+1].id_recurso,ambulancias[j+1],puntos[j+1].tipos_estados.alias2,puntos[j+1].disponible,puntos[j+1].servicio,j+1,rutas);
});
}
currentStep.done(function(){
Promise.resolve(sort_cars2(rutas)).then(function(resolve){
remove_ambulances();
create_ambulances(resolve);
mymap.spin(false);
mymap._handlers.forEach(function(handler) {
handler.enable();
});
});
});
}
function get_route2(id,punto,alias2,disponibilidad,servicio,i,rutas){
var defer = $.Deferred();
var waypoints = [
L.Routing.waypoint(L.latLng(feature.getLatLng().lat, feature.getLatLng().lng)),L.Routing.waypoint(L.latLng(punto.getLatLng().lat,punto.getLatLng().lng)),
];
var router = new L.routing.osrmv1({});
router.route(waypoints, function(error, routes) {
if(error==null){
rutas.push(routes[0]);
defer.resolve(i);
}
}, null, {});
return defer.promise();
}
var sort_cars2 = function(rutas) {
var promise = new Promise(function(resolve,reject){
rutas.sort(function(a, b){return a.summary.totalDistance-b.summary.totalDistance});
resolve(rutas);
});
return promise;
}
Then, I just call it using:
get_all_routes2();
Related
I'm using the H.platform.routingService().calculateIsoline method and had expected that the routeParams.departure property would have an effect on the result.
However, changing the date and/or time of day has no effect on the calculated isoline.
In the code below, startLocs is an array of geocode objects with lat and lng
let queryDateString = queryDate.format('YYYY-MM-DDTHH:mm:ss');
startLocs.forEach(loc => {
var routingParams = {
mode: 'fastest;car;',
start: `geo!${loc.geocode.lat},${loc.geocode.lng}`,
range: 600,
rangetype: 'time',
departure: queryDateString
};
// Define a callback function to process the isoline response.
var onResult = result => {
var center = new H.geo.Point(
result.response.center.latitude,
result.response.center.longitude
),
isolineCoords = result.response.isoline[0].component[0].shape,
linestring = new H.geo.LineString(),
isolinePolygon,
isolineCenter;
// Add the returned isoline coordinates to a linestring:
isolineCoords.forEach(function(coords) {
linestring.pushLatLngAlt.apply(linestring, coords.split(','));
});
// Create a polygon and a marker representing the isoline:
isolinePolygon = new H.map.Polygon(linestring);
isolineCenter = new H.map.Marker(center);
// Add the polygon and marker to the map:
this.markerGroup.addObject(isolineCenter);
this.polylineGroup.addObject(isolinePolygon);
};
// Get an instance of the routing service:
var router = this.platform.getRoutingService();
// Call the Routing API to calculate an isoline:
router.calculateIsoline(routingParams, onResult, function(error) {
console.log(error)
});
});
this.isLoading = false;
} catch (err) {
console.log('failed processing isochrones', err);
}
Regardless of the value of queryDateString in this example, the results are identical.
The documentation states that the ReST APIs query params map to properties in the routeParams so I expected that the departure property should have an effect. Does anyone know if that's not the case?
EDIT:
Updated to include working example in case anyone stumbles across this:
let queryDateString = queryDate.format('YYYY-MM-DDTHH:mm:ss');
let onResult = result => {
let center = new H.geo.Point(
result.response.center.latitude,
result.response.center.longitude
)
let isolineCoords = result.response.isoline[0].component[0].shape;
let linestring = new H.geo.LineString();
let isolinePolygon;
let isolineCenter;
// Add the returned isoline coordinates to a linestring:
isolineCoords.forEach(function(coords) {
linestring.pushLatLngAlt.apply(linestring, coords.split(','));
});
// Create a polygon and a marker representing the isoline:
isolinePolygon = new H.map.Polygon(linestring);
isolineCenter = new H.map.Marker(center);
//let isolineObj = [isolineCenter, isolinePolygon];
// Add the polygon and marker to the map:
this.markerGroup.addObject(isolineCenter);
this.polylineGroup.addObject(isolinePolygon);
};
let router = this.platform.getRoutingService();
startLocs.forEach(loc => {
let routingParams = {
mode: 'fastest;car;traffic:enabled',
start: `geo!${loc.geocode.lat},${loc.geocode.lng}`,
range: this.maxTime * 60,
rangetype: 'time',
departure: queryDateString
};
// Call the Routing API to calculate an isoline:
router.calculateIsoline(routingParams, onResult, function(error) {
alert(error.message);
});
});
}
catch (err) {
console.log('failed processing isochrones', err);
}
finally{
this.isLoading = false;
}
The mode is missing the traffic part. Please try to add this '&mode=fastest;car;traffic:enabled'. Then you will also get for example you sent a different shape for e.g. 10:00 am.
Here we have some extended example for visualizing isolines:
https://tcs.ext.here.com/examples/v3/isoline_routing
This might be interesting for you too.
I recently started development of a Node js application and it uses Selenium in a controller to fetch list of items from a web page and I want to return the fetched list of items as a JSON response.
exports.read_all_products = function (req, res) {
var driver = new webdriver.Builder().forBrowser('phantomjs').build();
driver.get('https://www.test.com/products?PC=' +req.params.category);
driver.wait(until.elementLocated(By.className('product-slide-all')), 20000, 'Could not locate the element within the time specified');
driver.findElements(By.className("product-slide-all")).then(function (elements) {
var arr = [];
elements.forEach(function (element) {
element.getAttribute("innerHTML").then(function (html) {
const dom = new JSDOM(html);
var obj = new Object();
obj.product_name = dom.window.document.querySelector(".product-name").textContent;
obj.product_code = dom.window.document.querySelector(".product-code").textContent;
obj.price = dom.window.document.querySelector(".product-price").textContent;
arr.push(obj);
});
});
res.json(arr);
});
}
Issue is I am always getting an empty JSON response even though items were added to the array. I want to know the proper way of handling this scenario.
Thanks.
It looks like the issue is because Selenium is running an async process, thus the response immediately returns because there is nothing blocking it.
findElements returns a Promise which you need to return the response from.
Take a look at How do I return the response from an asynchronous call?
Finally I was able to get it work with the help of webdriver.promise.map.
Moved web driver HTML extraction to separate function.
var findItems = function (category) {
var driver = new webdriver.Builder().forBrowser('phantomjs').build();
var map = webdriver.promise.map;
driver.get('https://www.test.com?PC=' + category);
driver.wait(until.elementLocated(By.className('product-slide-all')), 30000, 'Could not locate the element within the time specified');
var elems = driver.findElements(By.className("product-slide-all"));
return map(elems, elem => elem.getAttribute("innerHTML")).then(titles => {
return titles;
});
}
then call it from response handling function like bellow,
exports.read_all_products = function (req, res) {
findItems(req.params.category).then(function (html) {
var value;
var arr = [];
Object.keys(html).forEach(function (key) {
value = html[key];
const dom = new JSDOM(value);
var obj = new Object();
obj.product_name = dom.window.document.querySelector(".product-name").textContent;
obj.product_code = dom.window.document.querySelector(".product-code").textContent;
obj.price = dom.window.document.querySelector(".product-price").textContent;
arr.push(obj);
});
res.json(arr);
})
};
it's described in this stack overflow answers.
I need to create a search-filter function that show/hide google map markers accordingly to the filter.
For example, if i type "a" in my search form, the map will display only those markers that contain an "a", while the other remain hidden.
I'm using JS and knockout framework. I was thinking to use Marker.setVisible(true/false) but i do not know how to implement this feature.
Thanks for your help
var Data = {
locations: [
new Location("Palazzo Pitti", 43.765264, 11.250094,"4bc8c9d7af07a593d4aa812d"),
new Location("Uffizi Gallery", 43.768439, 11.2559,"51191cdfb0ed67c8fff5610b"),
new Location("Florence Cathedral", 43.773083, 11.256222,"4bd00cdb046076b00a576f71"),
new Location("Palazzo Vecchio", 43.769315, 11.256174,"4bd01b8077b29c74a0298a82"),
new Location("Piazza della Signoria", 43.7684152597, 11.2534589862,"4b81729af964a520a7a630e3"),
new Location("Giotto's Campanile", 43.772772, 11.255786,"4b49cd73f964a520d87326e3"),
new Location("Piazzale Michelangelo", 43.762462, 11.264897,"4b3276d5f964a520620c25e3"),
new Location("Ponte Vecchio", 43.768009, 11.253165,"4b6ed35df964a52038cc2ce3"),
new Location("Boboli Gardens", 43.762361, 11.248297,"4bc97abcfb84c9b6f62e1b3e"),
new Location("Vinci", 43.783333, 10.916667,"4ca4f0a0965c9c74530dc7fa"),
],
query: ko.observable(''),
};
// Search by name into the locations list.
Data.search = ko.computed(function() {
var self = this;
var search = this.query().toLowerCase();
return ko.utils.arrayFilter(self.locations, function(location) {
return location.title.toLowerCase().indexOf(search) >= 0;
});}, Data);
ko.applyBindings(Data);
}
You're pretty close to what you need. Remember that locations is a ko.observable, so you need to use parens to open it up. Try this:
Data.search = ko.computed(function() {
var self = this;
var search = this.query().toLowerCase();
return ko.utils.arrayFilter(self.locations, function(location) {
if (location.title.toLowerCase().indexOf(search) >= 0) {
location.setVisible(true);
return true;
}
else {
place.setVisible(false);
return false;
}
});
}, Data); // not sure you really need this last reference to Data here
My app is looking up google place details and displaying some of the information. I have a list of place id's in a json file broken down by type of establishment. A factory accesses and makes available the ids to the controller. I also have a service that loops through all the id's, looking up the details and adding them to an object that is made available to the controller.
I can get it to work in the sense that I can access the json data, look up the details, and return the object. However, no matter how I do it, if I try and return multiple objects, one for each type of business, I get all the businesses together or an error (more on that in a minute).
I have structured this a number of ways but I will show the code for 2 ways that I have tried. I'm new to Angular so I may have this completely wrong and not even using services and factories correctly so please go easy on me.
locations.json
{
"restaurants": {
"Michaels": "ChIJwaTJAL4n5IgRgyJupbpQhjM",
"Collage": "ChIJw5HgNzAm5IgRqbkEqKXIpC4",
"Scarlet": "ChIJT9ImkZUn5IgREb1hYwKA1Nc",
"Maya": "ChIJofgqBJYn5IgRVa-HQvp6KDk",
"Ice": "ChIJnXpQpewn5IgR7k9yxWXUu1M",
"Sangrias": "ChIJITcc_ZUn5IgR90iEna6FRGM",
"Columbia": "ChIJ8xR18JUn5IgRfwJJByM-quU",
"Harrys": "ChIJ8aLBaJYn5IgR60p2CS_RHIw"
},
"bars":
{
"Scarlet": "ChIJT9ImkZUn5IgREb1hYwKA1Nc",
"Lion": "ChIJqVCL_b0n5IgRpVR5CFZWi4o",
"Tradewinds": "ChIJpwF4ZJYn5IgRTDzwBWvlSIE",
"Ice": "ChIJnXpQpewn5IgR7k9yxWXUu1M",
"Stogies": "ChIJlwkiApYn5IgR6XVFMyqLAS4",
"Rondeazvous": "ChIJkz3V7pUn5IgRQhui26imF1k",
"Meehan": "ChIJK8NZGZYn5IgRA91RrGETwrQ",
"Sangrias": "ChIJITcc_ZUn5IgR90iEna6FRGM",
"NoName": "ChIJA-VeCb4n5IgRmbuF8wdOGaA",
"StGeorge": "ChIJ4yo36JUn5IgRXgiRD7KMDe0"
}
}
Method 1
locations.js
angular.module('app.locations', [])
.factory('restsFact', function($http){
var restaurants = [];
return {
getRests: function(){
return $http.get('locations.json').then(function(response){
restaurants = response.data.restaurants;
return restaurants;
});
}
};
})
.factory('barsFact', function($http){
var bars = [];
return {
getBars: function() {
return $http.get('locations.json').then(function(response){
bars = response.data.bars;
return bars;
});
}
};
})
.service('locationsService', function (ngGPlacesAPI) {
var x, id, details, push, placeDetails = [];
// Takes list of specific type of locations as argument and looks up Place details for each location
this.details = function(type) {
for (x in type) {
if (type.hasOwnProperty(x)) {
id = type[x];
ngGPlacesAPI.placeDetails({placeId: id}).then(push);
}
}
return placeDetails;
};
push = function (data) {
details = data;
placeDetails.push(details);
};
});
Controllers
.controller('RestCtrl', function($scope, locationsService, restsFact) {
// Location Details Object
restsFact.getRests().then(function(locs){
$scope.restaurants= locationsService.details(locs);
});
})
//
// Bar Controller
//
.controller('BarsCtrl', function($scope, locationsService, barsFact){
// Locations Details Object
barsFact.getBars().then(function(locs){
$scope.bars = locationsService.details(locs);
});
})
Method 2
With this method I can load one page but if I move to the next I get an error: [$rootScope:inprog] $digest already in progress. I read up on the error and get the idea of why I get it but just not sure how to go about fixing it.
locations.js
angular.module('app.locations', [])
.factory('locationsFact', function($http){
var locations = [];
return {
getlocations: function(){
return $http.get('locations.json').then(function(response){
locations = response;
return locations;
});
}
}
})
.service('locationsService', function (ngGPlacesAPI) {
var x, id, details, push, placeDetails = [];
// Takes list of specific type of locations as argument and looks up Place details for each location
this.details = function(type) {
for (x in type) {
if (type.hasOwnProperty(x)) {
id = type[x];
ngGPlacesAPI.placeDetails({placeId: id}).then(push);
}
}
return placeDetails;
};
push = function (data) {
details = data;
placeDetails.push(details);
};
});
Controller
.controller('locationsCtrl', function($scope, locationsService, locationsFact){
// Locations Details Object
locationsFact.getlocations().then(function(locs){
$scope.restaurants = locationsService.details(locs.data.restaurants);
$scope.bars = locationsService.details(locs.data.bars);
});
})
So I read a lot over the last week and learned a lot as well. I completely rewrote that mess up above into something resembling decent code, there were a lot of problems with it originally. I got everything working anyway. Here is how it looks now.
Factory
angular.module('app.factories', [])
.factory('data', function($http){
// Get JSON With Place ID's and create array of
// place id objects for each category
var places = {};
places.ids = function(){
return $http.get('locations.json')
.success(function(data){
places.rests = data.restaurants;
places.bars = data.bars;
places.lodg = data.lodging;
places.att = data.attractions;
});
};
return places;
})
.factory('details', function(ngGPlacesAPI, $q){
var details = {};
// Split ID Array into array of arrays <= 10.
// Google won't return more than 10 details request at one time.
details.process = function(type) {
var idSets = {},
size = 10,
i, j, k;
for (i=0, j=type.length, k=0; i<j; i+=size){
idSets[k] = type.slice(i, i+size);
k++;
}
return idSets;
};
// Lookup Details by Place ID
// Loop through ID array and return array of details objects
details.getDetails = function(idSet, pageNum) {
var page = idSet[pageNum],
promises = [];
for(var i=0; i<page.length; i++) {
promises.push(ngGPlacesAPI.placeDetails({placeId: page[i][i]}));
}
return $q.all(promises);
};
// Return Details Object
return details;
});
Controller
//
// Restaurants Controller
//
.controller('restaurantsCtrl', function(details, data, $scope) {
var vm = this;
// Get JSON file with placeIds and set some variables
data.ids().then(function() {
var page = details.process(data.rests),
pageNum = 0,
numPages = page.length;
vm.moreData = true;
// Loads more place details on scroll down
vm.loadMore = function() {
if (pageNum <= numPages - 1) {
pageNum++;
details.getDetails(page, pageNum).then(function(response) {
vm.rests.push(response);
vm.$broadcast('scroll.infiniteScrollComplete');
});
}else{vm.moreData=false}
};
// Load first page of place details
details.getDetails(page, pageNum).then(function(response){
vm.rests = response;
console.log(vm.rests);
});
// Watches for when to load more details
$scope.$on('$stateChangeSuccess', function(){
vm.loadMore();
});
});
})
I'm building an offline HTML page using Angular and using ydn-db for offline storage.
I have a database service like so,
demoApp.SericeFactory.database = function database() {
var database = {
dataStore: null,
admins: [],
students: [],
errors: [],
getadmindata: function(username) {
self = null, that = this
database.dataStore.get('admins', username).done(function(record) {
that.self = record;
return record;
}).fail(function(e) {
console.log(e);
database.errors.push(e);
});
return self; //This does not change.
}
};
database.dataStore = new ydn.db.Storage('DemoApp');
angular.forEach(INITSTUDENTS, function(student) {
database.dataStore.put('students', student, student.matricno);
database.students.push(student);
});
angular.forEach(INITADMINS, function(admin) {
database.dataStore.put('admins', admin, admin.username);
database.admins.push(admin);
});
return database;
I also have a controller that attempts to use the database;
function AppCntl ($scope, database) {
var user = database.getadmindata('user'); //I get nothing here.
}
What I have tried,
I have tried making changing self to var self
I have tried splitting the function like so
rq = database.dataStore.get('admins', 'user');
rq.done(function(record), {
self = record;
alert(self.name) //Works.
});
alert(self) //Doesn't work.
I have gone through questions like this o StackOverflow but nothings seems to be working for me or maybe I have just been looking in the wrong place.
Database request are asynchronous and hence it executes later after end of execution of the codes.
So when the last alert execute, self is still undefined. Secound alert execute after db request completion and it is usual right design pattern.
EDIT:
I have success with following code:
// Database service
angular.module('myApp.services', [])
.factory('database', function() {
return new ydn.db.Storage('feature-matrix', schema);
}
});
// controller using database service
angular.module('myApp.controllers', [])
.controller('HomeCtrl', ['$scope', 'utils', 'database', function($scope, utils, db) {
var index_name = 'platform, browser';
var key_range = null;
var limit = 200;
var offset = 0;
var reverse = false;
var unique = true;
db.keys('ydn-db-meta', index_name, key_range, limit, offset, reverse, unique)
.then(function(keys) {
var req = db.values('ydn-db', keys);
req.then(function(json) {
$scope.results = utils.processResult(json);
$scope.$apply();
}, function(e) {
throw e;
}, this);
});
}])
Complete app is available at https://github.com/yathit/feature-matrix
Running demo app is here: http://dev.yathit.com/demo/feature-matrix/index.html