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.
Related
I am using real time database as firebase and i used geofire javascript package to get near by data from firebase. but some time it automatically gives error like :
RangeError: Maximum call stack size exceeded
at ChildrenNode.isLeafNode (api/node_modules/firebase-admin/node_modules/#firebase/database-compat/dist/index.standalone.js:8956:50)
at ChildrenNode.equals (api/node_modules/geofire/dist/geofire/index.cjs.js:8461:24)
this is my sample query to find near by things from firebase realtime database using geofire
while (some amount) {
geoDriverId = await(new Promise((resolve, reject) => {
try {
geoDriverId = []
geoDriverIds = []
const geoQuery = config.firebase.table.query({
center: [parseFloat(pick_up_latitude), parseFloat(pick_up_longitude)],
radius: i
})
let listener = geoQuery.on("key_entered", (key, location, distance) => {
geoDriverIds.push({ key, distance });
});
geoQuery.on("ready", (key, location, distance) => {
listener.cancel();
geoQuery.cancel();
resolve(geoDriverIds);
return;
});
} catch (e) {
console.log("e", e)
}
}))
}
If the intention is to poll for the initial results of the query, consider using the following method instead (this is effectively the code you provided in the question, cleaned up and turned into a method):
// Executes a GeoFire query around the given center and returns only the initial results as a Promise.
function pollGeofireLocation(center: Geopoint, radius: number) {
return new Promise(resolve => {
const queryResults = [],
geoQuery = config.firebase.table.query({ center, radius });
geoQuery.on("key_entered", (key, location, distance) => {
queryResults.push({ key, distance }); // consider storing location
});
geoQuery.on("ready", () => {
geoQuery.cancel(); // unsubscribe all event listeners and destroy query
// consider sorting queryResults before resolving the promise (see below)
resolve(queryResults);
});
});
}
Usage:
const pick_up_geopoint = [parseFloat(pick_up_latitude), parseFloat(pick_up_longitude)];
const results = await pollGeofireLocation(pick_up_geopoint, 10);
// results is an array of { key: string, distance: number } objects,
// in the order they were discovered by the query - not necessarily by distance
To sort the results by the distance from the center point:
const pick_up_geopoint = [parseFloat(pick_up_latitude), parseFloat(pick_up_longitude)];
const nearbyDriverIDs = await pollGeofireLocation(pick_up_geopoint, 10);
// nearbyDriverIDs is an array of { key: string, distance: number } objects,
// in the order they were discovered by the query - not necessarily by distance
nearbyDriverIDs.sort(({ distance: a_dist }, { distance: b_dist }) => a_dist - b_dist);
// nearbyDriverIDs is now an array of { key: string, distance: number } objects,
// ordered by their distance from the center point, closest to furthest.
// nearbyDriverIDs[0], if present (nearbyDriverIDs may be empty!), is the closest driver
If the intention is to find only the closest result, you can use the following method to progressively search for results:
// Executes a GeoFire query around the given center, performing an expanding search where the first result is returned in a Promise.
function findClosestResultToGeofireLocation(center: Geopoint, maxRadius: number, stepRadius: number = 5, startRadius: number = 0) {
return new Promise((resolve, reject) => {
const queryResults = [],
geoQuery = config.firebase.table.query({
center,
radius: Math.min(maxRadius, startRadius || stepRadius) // if startRadius is provided, use its value, otherwise start at stepRadius
});
geoQuery.on("key_entered", (key, location, distance) => {
queryResults.push({ key, distance }); // consider storing location
});
geoQuery.on("ready", () => {
// found at least one result? return the closest one
if (queryResults.length > 0) {
geoQuery.cancel(); // unsubscribe all event listeners and destroy query
const closest = queryResults
.reduce((closest, result) => result.distance < closest.distance ? result : closest);
resolve(closest);
return;
}
// no results for search criteria and at max search radius? reject with an error
if (geoQuery.radius() >= maxRadius) {
geoQuery.cancel(); // unsubscribe all event listeners and destroy query
reject(new Error(`No results found within ${maxRadius}km`));
// could also use resolve(null);
return;
}
// expand search area by increasing the radius by stepRadius kilometers,
// stopping at maxRadius kilometers
// note: you may want to add a delay before calling this
geoQuery.updateCriteria({
radius: Math.min(maxRadius, geoQuery.radius() + stepRadius)
});
});
});
}
Usage:
const pick_up_geopoint = [parseFloat(pick_up_latitude), parseFloat(pick_up_longitude)];
const closestDriverID = await findClosestResultToGeofireLocation(pick_up_geopoint, 25);
// closestDriverID will be the closest driver to pick_up_geopoint.
// With the above arguments, the search will try for drivers <5km away, then
// <10km away, then <15km away, then <20km away and then finally <25km away.
// If a driver is not in range, the returned promise will reject with an error.
I'm a newby with JS and OL, and I have an Openlayers 6 map with different WMS layers grouped into several ol.layer.Group. I want to request feature information through "getGetFeatureInfoUrl". Visibility of layers can be turned on/off in the layer tree. I'd like to, upon clicking somewhere in the map:
Get Feature Info ONLY for layers that are currently visible
and, if there are more than one layers at the chosen location, get the Feature Info for all of them
This code works well for just one layer (wmsLayer5, in this case)
map.on('singleclick', function (evt) {
if (!wmsLayer5.getVisible()) return;
document.getElementById('info').innerHTML = '';
const viewResolution = /** #type {number} */ (vista.getResolution());
const url = wmsLayer5.getSource().getFeatureInfoUrl(
evt.coordinate,
viewResolution,
'EPSG:25830',
{'INFO_FORMAT': 'text/html'}
);
if (url) {
fetch(url)
.then((response) => response.text())
.then((html) => {
document.getElementById('info').innerHTML = html;
});
}
});
But I need a code to iterate over all layers and only call getFeatureInfo if they are visible. I've tried this one, but doesn't work and doesn't return any message in the console
map.on('singleclick', function (evt1) {
document.getElementById('info').innerHTML = '';
var viewResolution = /** #type {number} */
(vista.getResolution());
var url = '';
document.getElementById('info').innerHTML ='';
layers.forEach(function (layer, i, layers) {
if (layer.getVisible() ) {
url = layer.getSource().getGetFeatureInfoUrl(
evt1.coordinate,
viewResolution,
'EPSG:25830', {
'INFO_FORMAT': 'text/html',
'FEATURE_COUNT': '300'
});
if (url) {
fetch(url)
.then((response) => response.text())
.then((html) => {
document.getElementById('info').innerHTML += html;
});
}
}
});
});
Any suggestion to fix it?
This method has been deprecated since version 6.13 (4 months ago) and won't be changed before next major release. I didn't use suggested replacement layer.getData(). Following code iterates through all the layers and queries only visible ones and of type Image.
map.on("click", onMouseClick);
function onMouseClick(browserEvent) {
let coordinate = browserEvent.coordinate;
let pixel = map.getPixelFromCoordinate(coordinate);
map.forEachLayerAtPixel(pixel, function (layer) {
if (layer instanceof ol.layer.Image) {
if (layer.get("visible")) {
let url = layer.getSource().getFeatureInfoUrl(coordinate, map.getView().getResolution(), "EPSG:3857", {
INFO_FORMAT: "application/json",
});
if (url) {
fetch(url)
.then(function (response) {
return response.text();
})
.then(function (json) {
let data = JSON.parse(json);
if (data.features.length > 0) {
//Do something with data.features
}
});
}
}
}
});
}
I'm a french student in computering and I have to use Mapbox but since I create a class I'm stuck by this error.When I wasn't in a class it worked perfectly but now it's fully broken.And I saw on some topics it could come from safari but I already tested it on Mozilla and it still broken.
This is my class.
constructor() {
//Temporary array of currentMarkers
let currentMarkers=[];
let type ="";
//Create the map
mapboxgl.accessToken = 'private data';
this.mapbox = new mapboxgl.Map({
container: 'map', // container id
style: 'mapbox://styles/mapbox/streets-v11',
center: [-74.5, 40], // starting position
zoom: 9 // starting zoom
});
//Add search bar from a plugin
let geocoder = new MapboxGeocoder({
accessToken: mapboxgl.accessToken,
placeholder: 'Saissisez une adresse', // Placeholder text for the search bar
marker: {
color: 'orange'
},
mapboxgl: mapboxgl
});
this.mapbox.addControl(geocoder);
const mbox = this;
this.mapbox.on("click",function(){
this.getcoordonates();
});
//Allow us to create marker just with a research
geocoder.on('result', function(e) {
//Deleting all current markers
if (currentMarkers!==null) {
for (let i = currentMarkers.length - 1; i >= 0; i--) {
currentMarkers[i].remove();
}
}
//Add the markers who come with the research
this.addMarker(e.result.center[0],e.result.center[1]);
// Delete the last marker who got placed
//currentMarkers[currentMarkers.length - 1].remove();
});
this.addPoint(2.333333 ,48.866667 ,"supervisor");
}
//split the JSON.stringify(e.lngLat.wrap()) to get the coordinates
async mysplit(m){
let z = m.split(',');
let x = z[0].replace("{\"lng\":","");
let g = z[1].replace("\"lat\":","");
let y = g.replace("}","");
await addMarker(x,y);
}
//Add a marker on click after the excution of mysplit() function
async addMarker(x,y) {
// tmp marker
let oneMarker= new mapboxgl.Marker()
.setLngLat([x,y])
.addTo(this.mbox);
currentMarkers.push(oneMarker);
}
// Get the coordinates and send it to split function
getcoordonates(){
mbox.on('click',function(e){
let m = JSON.stringify(e.lngLat.wrap());
this.mbox.mysplit(m);
});
}
addPoint(y,x,type)
{
let color ="";
let t = type;
if(t == "supervisor")
{
color = "grey";
}else if (t =="fieldworker") {
color = "red";
}else if (t == "intervention") {
color = "blue";
}else alert("Nous ne pouvons pas ajouter votre marker\nLatitude : "+x+"\nLongitude :"+y+"\n car "+t+" n'est pas un type reconnu" );
let myMarker = new mapboxgl.Marker({"color": color})
.setLngLat([y,x])
.addTo(this.mbox);
}
}
Thanks for help and have a good day :) ! Sorry if my English isn't that good.
As the first step, the
this.mapbox.on("click", function(e){
this.getcoordonates();
});
was called outside of any method of the Map class. It would most probably belong to the constructor (as discussed above in the question's comments section).
Next, the callback changes the scope of this, so it is not anymore pointing to a Map instance. A common solution to this issue is to store/backup this before, something like:
constructor() {
...
const thisObject = this;
this.mapbox.on("click", function(e) {
thisObject.getcoordonates();
});
}
Update:
The logic of code in its current form tries to add a new click event listener every time the map is clicked. Which is not desired. This getcoordonates() function is not really needed. Instead this should work (never tested it, it is based on your code):
constructor() {
...
const mbox = this;
this.mapbox.on("click", function(e) {
let m = JSON.stringify(e.lngLat.wrap());
mbox.mysplit(m);
});
}
Remarks:
There is no real logic behind JSON-encoding the lngLat object before calling mysplit just to decode it there.
You won't need it here, but the reverse of the JSON.stringify() is JSON.parse(). There is no need to work with it on a string level, like the current mysplit() method does.
Instead, the mysplit() method should be called directly with the e.lngLat object as its argument.
Going further, since there is no "splitting" (decoding) really needed, the mysplit() method isn't really needed either.
In the end, something like this should work:
constructor() {
...
const mbox = this;
this.mapbox.on("click", function(e) {
await mbox.addMarker(e.lngLat.lng, e.lngLat.lat);
});
}
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();
I'm trying to set up a game that allows playing with random players. The code below is supposed to create a GameMessage object for both paired players. To relate both objects as part of the same game, I've decided to save the objectId of of the game made for "firstplayer" in the field "otherside" for "secondplayer" and vice-versa. For some reason (perhaps the first save of firstplayer and secondplayer isn't done before the code attempts to retrieve the objectIds, meaning there are no objectIds to get?).
Short version: Why are the "otherside" values not saving?
Parse.Cloud.define("findpartner", function(request, response) {
var User = Parse.Object.extend("_User");
var user = new User();
var currentuser = Parse.User.current();
currentuser.set("searching", 0);
var query = new Parse.Query(User);
query.equalTo("searching", 1);
query.limit(50); //limit to at most 50 users
query.find({
success: function(objects) {
var amount = objects.length;
var indexNum = Math.floor((Math.random() * amount));
var newpartner = objects[indexNum];
if (amount > 0 && newpartner.id !=currentuser.id) {
newpartner.set("searching", 0);
var Firstplayer = Parse.Object.extend("GameMessages");
var firstplayer = new Firstplayer();
var Secondplayer = Parse.Object.extend("GameMessages");
var secondplayer = new Secondplayer();
firstplayer.set("sender", currentuser.id);
firstplayer.set("receiver", newpartner.id);
firstplayer.set("sent",0);
firstplayer.set("received",0);
firstplayer.set("receiverName", newpartner.getUsername());
secondplayer.set("sender", newpartner.id);
secondplayer.set("receiver", currentuser.id);
secondplayer.set("sent",0);
secondplayer.set("received",0);
secondplayer.set("receiverName", currentuser.getUsername());
firstplayer.save().then(function(secondplayer){ <<<
return secondplayer.save(); <<<
}).then(function(firstplayer_update) { <<<
return firstplayer.save({ otherside: secondplayer.id}); <<<
}).then(function(secondplayer_update){ <<<
return secondplayer.save({ otherside: firstplayer.id}); <<<
});
newpartner.save(null, {useMasterKey: true});
}
else {
currentuser.set("searching", 1);
}
currentuser.save();
response.success(amount);
},
error: function(error) {
alert("Error: " + error.code = " " + error.message);
}
});
});
I added arrows to show where the "otherside" is. They're not in the actual code. I do not doubt the code has mistakes though, I do not know javascript. I wrote it solely by studying the parse.com documentation.
I'm not convinced that it makes sense to create these 2 independent messages and link them together, but I won't let that stand in the way of getting this working. This isn't tested, but I've refactored your code and think you should try to glean a few things from it.
// Set this up once, outside of your function, and use it everywhere
var GameMessage = Parse.Object.extend("GameMessages");
Parse.Cloud.define("findpartner", function(request, response) {
// Code defensively, make sure this function requires a user be logged in.
if (!request.user) {
console.log("non-user called findpartner");
return response.error("Unauthorized.");
}
// Get the user who called the function
var user = request.user;
// The end response is a number, apparently
var result = 0;
// The target player
var targetPlayer;
// The two messages that will be used if a match is found
var firstmsg = new GameMessage();
var secondmsg = new GameMessage();
// Create a Users query
var query = new Parse.Query(Parse.User);
query.equalTo("searching", 1);
query.notEqualTo("objectId", user.id);
query.limit(50);
// Remove public access to Find operations for Users in the Data Browser
// Use the master key to query, and use promise syntax.
query.find({ useMasterKey: true }).then(function(objects) {
result = objects.length;
// If no users were found searching, mark the user as searching and save
if (result == 0) {
user.set('searching', 1);
// Return the save promise
return user.save(null, { useMasterKey: true });
}
// Pick a random user out of the response
var indexNum = Math.floor((Math.random() * objects.length));
var targetPlayer = objects[indexNum];
// Set that user to no longer be searching and save
targetPlayer.set("searching", 0);
return targetPlayer.save(null, { useMasterKey: true }).then(function() {
firstmsg.set("sender", user.id);
firstmsg.set("receiver", targetPlayer.id);
firstmsg.set("sent", 0);
firstmsg.set("received", 0);
firstmsg.set("receiverName", targetPlayer.getUsername());
secondmsg.set("sender", targetPlayer.id);
secondmsg.set("receiver", user.id);
secondmsg.set("sent", 0);
secondmsg.set("received", 0);
secondmsg.set("receiverName", user.getUsername());
// Return the promise result of saving both messages
return Parse.Object.saveAll([firstmsg, secondmsg], { useMasterKey: true });
}).then(function(messages) {
// Set the pointers to reference each other
firstmsg.set("otherside", secondmsg.id);
secondmsg.set("otherside", firstmsg.id);
// Return the promise result of saving both messages, again
return Parse.Object.saveAll([firstmsg, secondmsg], { useMasterKey: true });
});
}).then(function() {
// All the stuff above has finished one way or the other, now we just need to
// send back the result. 0 if no match was made.
response.success(result);
}, function(error) {
response.error(error);
});
});
firstplayer.save();
secondplayer.save();
secondplayer.set("otherside",firstplayer.id); <<<
firstplayer.set("otherside",secondplayer.id); <<<
firstplayer.save();
secondplayer.save();
This is the part of code that you say not working. In parse doc you can see that .save() is a non blocking operation. Means the line firstplayer.save() goes immediately to next line(it wont block the thread for saving). So when you set id secondplayer.set("otherside",firstplayer.id) firstplayer.id is still undefined.
So if you want a synchronous logic, like save first_object then save second_object ,
you have to use call backs.
first_object.save({
success: function(saved_first_object) {
second_object.save({
success: function(saved_second_object) {
//process complete
},
failure: function(error){
}
})
},
failure: function(error) {
console.log(error);
}
})
You can also approach it using promises.
http://blog.parse.com/2013/01/29/whats-so-great-about-javascript-promises/
UPDATE: Based on question edit from OP trying promises
Try this
firstplayer.save()
.then(function(saved_firstPlayer){
firstplayer = saved_firstPlayer;
return secondplayer.save();
}).then(function(saved_secondplayer) {
secondplayer = saved_secondplayer;
return firstplayer.save({ otherside: secondplayer.id});
}).then(function(updated_firstplayer){
firstplayer = updated_firstplayer;
return secondplayer.save({ otherside: firstplayer.id});
}).then(function(updated_secondlayer){
secondplayer= update_secondplayer;
});