When does mapquest route finish rendering? - leaflet

I am using MapQuest map with leaflet. The routes are being injected dynamically. When we add routes to the map, it takes time to finish the rendering.
I want to block the screen while the map is being rendered.
Is there any way(mapquest API or leaflet event) to know the map is ready or finished rendering so that I can stop the blocking of the screen.
I'm using Leaflet to add the routes. Something like this:
function (locations) {
const dir = MQ.routing.directions();
dir.route({ locations: locations });
comp.mqroute = MQ.routing.routeLayer({
directions: dir,
fitBounds: true,
draggable: false,
});
comp.map.addLayer(comp.mqroute);
}

This is a case of RTFM.
The fact that you're using MQ.routing.directions in your code tells me that you're using the Mapquest routing plugin for Leaflet, which has complete API documentation.
By reading that API documentation, one can notice a success event, I quote:
success
Fired when route data has been retrieved from the server and shapePoints have been decompressed. [...]
I have a heavy suspicion that you don't need to know when the route is rendered (meaning: when the lines have been displayed on the screen) but rather when then network requests for the route are finished (which is what takes most time). The time to actually render lines on the screen is usually negligible unless one is working with thousands of segments (after the automatic douglas-peucker simplification).
I also have a heavy suspicion that this is a XY problem, and that the root problem you're trying to solve is race conditions when the user is (re-)requesting routes too fast, to which the solution is just throttling the requests rather than blocking the UI.
How to use this success event is illustrated in MapQuest's Route Narrative example:
dir = MQ.routing.directions();
dir.on('success', function(data) {
// ...
// the code here will run once the route is ready
// ...
}

Related

Avoiding repetitive calls when creating reactfire hooks

When initializing a component using reactfire, each time I add a reactfire hook (e.g. useFirestoreDocData), it triggers a re-render and therefore repeats all previous initialization. For example:
const MyComponent = props => {
console.log(1);
const firestore = useFirestore();
console.log(2);
const ref = firestore.doc('count/counter');
console.log(3);
const { value } = useFirestoreDocDataOnce(ref);
console.log(4);
...
return <span>{value}</span>;
};
will output:
1
1
2
3
1
2
3
4
This seems wasteful, is there a way to avoid this?
This is particularly problematic when I need the result of one reactfire hook to create another (e.g. retrieve data from one document to determine which other document to read) and it duplicates the server calls.
See React's documentation of Suspense.
Particulary that part: Approach 3: Render-as-You-Fetch (using Suspense)
Reactfire uses this mechanics. It is not supposed to fetch more than one time for each call even if the line is executed more than once. The mechanics behind "understand" that the fetch is already done and will start the next one.
In your case, react try to render your component, see it needs to fetch, stop rendering and show suspense's fallback while fetching. When fetch is done it retry to render your component and as the fetch is completed it will render completely.
You can confirm in your network tab that each calls is done only once.
I hope I'm clear, please don't hesitate to ask for more details if i'm not.

HERE API:Swift. Wierd behaviour : Loops in a route - Creating a route based on GPS trace creates loops. / passthrough waypoints

im trying to create a route that follows a gps trace i provide.
The gps trace is cleaned up and it has no loops in it and is in correct order.
I checked it with other services.
It has 1920 points.
You can find the trace here GPX Files
Sadly if i create a route based on provided sdk example (github) i get loops in my path.
I was hoping you could help me to solve following problems:
how do i avoid loops while creating route by using HERE ios Swift SDK
how do i set route options is such way to follow provided point array and not create a fastest or balanced route.
Since i could not find those functions in Ios sdk i used additional REST API to filter the route a bit to remove all points that were not matched correctly according to here maps... before drawing the route.. ie everything with low probability, warnings, big distance to the road... yet the result is still not good. Here is a cleaned up file.. the file is being created after the original was maped / run once through HERE Maps. In this file all points that have low confidence or produce warnings or have big distance to original points .. are removed. This is the one i use to create a route and it still have the same issues like loops and weird turns.
Thank you very much in advance!
BR.
So far i have this code:
private lazy var router = NMACoreRouter()
#objc func do_routing_stuff( gps_trace :[NMAWaypoint]) {
var stops = [Any]()
stops = gps_trace
let routingMode = NMARoutingMode(routingType: .fastest,
transportMode: .car,
routingOptions: .avoidHighway)
// Trigger the route calculation
router.calculateRoute(withStops: stops ,
routingMode: routingMode)
{ [weak self] routeResult, error in
guard error == .none else {
self?.showMessage("Error:route calculation returned error code \(error.rawValue)")
return
}
guard let result = routeResult, let routes = result.routes, routes.count > 0 else {
self?.showMessage("Error:route result returned is not valid")
return
}
// Let's add the 1st result onto the map
self?.route = routes[0]
self?.updateMapRoute(with: self?.route)
// self?.startNavigation()
}
}
private func updateMapRoute(with route: NMARoute?) {
// remove previously created map route from map
if let previousMapRoute = mapRoute {
mapView.remove(mapObject:previousMapRoute)
}
guard let unwrappedRoute = route else {
return
}
mapRoute = NMAMapRoute(unwrappedRoute)
mapRoute?.traveledColor = .clear
_ = mapRoute.map{ mapView?.add(mapObject: $0) }
// In order to see the entire route, we orientate the
// map view accordingly
if let boundingBox = unwrappedRoute.boundingBox {
geoBoundingBox = boundingBox
mapView.set(boundingBox: boundingBox, animation: .linear)
}
}
in comparison same route presented with leaflet maps.
I believe the problem you have is that you are feeding the Routing API a large number of waypoints, all of which are in close proximity to each other.
You have almost 2000 waypoints in your GPX file (and ~1300 in your cleaned one). Each of these waypoints is less than 10 meters distance from their closest neighbors. This is not the type of data that the Routing API is really designed to work with.
I've experimented with your GPX Trace and I have come up with the following solution: simply skip a bunch of coordinates in your trace.
First, clean up your trace using the Route Matching API (which I believe you have been doing).
Second, pick the first trkpt in the GPX file as your first waypoint for the Routing call. Then skip the next 20 points. Pick the following trkpoint as the second waypoint. Repeat this until you are at the end of the file. Then add the last trkpt in the trace as the final waypoint.
Then call the Routing API and you should get a good match between your trace and your route, without any loops or other weird routing artefacts.
Some notes:
I have picked 20 as the number of traces to skip, because this would put about 200m in between each waypoint. That should be close enough to ensure that the Routing API does not deviate too much from the traced route. For larger traces you may wish to increase that number. For traces in urban environments with lots alternate routes, you may want to use a smaller number.
It's important to clean the data with the Route Matching API first, to avoid picking outliers as waypoints.
Also, you may not wish to use the "avoidHighways" option. Given your use case, there doesn't seem to be a benefit and I could see it causing additional problems.
By now you probably worked it out, but your waypoints are likely landing on bridges or tunnels that are along your route but not on the road you want. I.e. the waypoint is intended to be on the road under the bridge but the routing engine perceives that you want to drive on the bridge.
The routing engine is looping around those roads to drive you on that waypoint on the bridge or in the tunnel.
There is no simple solution to this that I have found.

Wait for backend service response before making changes to ag-grid table

I am using ag-grid/ag-grid-angular to provide an editable grid of data backed by a database. When a user edits a cell I want to be able to post the update to the backend service and if the request is successful update the grid and if not undo the user's changes and show an error.
I have approached this problem from a couple different angles but have yet to find the solution that meets all my requirements and am also curious about what the best practice would be to implement this kind of functionality.
My first thought was to leverage the cellValueChanged event. With this approach I can see the old and new values and then make a call to my service to update the database. If the request is successful then everything is great and works as expected. However, if the request fails for some reason then I need to be able to undo the user's changes. Since I have access to the old value I can easily do something like event.node.setDataValue(event.column, event.oldValue) to revert the user's changes. However, since I am updating the grid again this actually triggers the cellValueChanged event a second time. I have no way of knowing that this is the result of undoing the user's changes so I unnecessarily make a call to my service again to update the data even though the original request was never successful in updating the data.
I have also tried using a custom cell editor to get in between when the user is finished editing a cell and when the grid is actually updated. However, it appears that there is no way to integrate an async method in any of these classes to be able to wait for a response from the server to decide whether or not to actually apply the user's changes. E.g.
isCancelBeforeStart(): boolean {
this.service.updateData(event.data).subscribe(() => {
return false;
}, error => {
return true;
});
}
does not work because this method is synchronous and I need to be able to wait for a response from my service before deciding whether to cancel the edit or not.
Is there something I am missing or not taking in to account? Or another way to approach this problem to get my intended functionality? I realize this could be handled much easier with dedicated edit/save buttons but I am ideally looking for an interactive grid that is saving the changes to the backend as the user is making changes and providing feedback in cases where something went wrong.
Any help/feedback is greatly appreciated!
I understand what you are trying to do, and I think that the best approach is going to be to use a "valueSetter" function on each of your editable columns.
With a valueSetter, the grid's value will not be directly updated - you will have to update your bound data to have it reflected in the grid.
When the valueSetter is called by the grid at the end of the edit, you'll probably want to record the original value somehow, update your bound data (so that the grid will reflect the change), and then kick off the back-end save, and return immediately from the valueSetter function.
(It's important to return immediately from the valueSetter function to keep the grid responsive. Since the valueSetter call from the grid is synchronous, if you try to wait for the server response, you're going to lock up the grid while you're waiting.)
Then, if the back-end update succeeds, there's nothing to do, and if it fails, you can update your bound data to reflect the original value.
With this method, you won't have the problem of listening for the cellValueChanged event.
The one issue that you might have to deal with is what to do if the user changes the cell value, and then changes it again before the first back-end save returns.
onCellValueChanged: (event) => {
if (event.oldValue === event.newValue) {
return;
}
try {
// apiUpdate(event.data)
}
catch {
event.node.data[event.colDef.Field] = event.oldValue;
event.node.setDataValue(event.column, event.oldValue);
}
}
By changing the value back on node.data first, when setDataValue() triggers the change event again, oldValue and newValue are actually the same now and the function returns, avoiding the rather slow infinite loop.
I think it's because you change the data behind the scenes directly without agGrid noticing with node.data = , then make a change that agGrid recognises and rerenders the cell by calling setDataValue. Thereby tricking agGrid into behaving.
I would suggest a slightly better approach than StangerString, but to credit him the idea came from his approach. Rather than using a test of the oldValue/newValue and allowing the event to be called twice, you can go around the change detection by doing the following.
event.node.data[event.colDef.field] = event.oldValue;
event.api.refreshCells({ rowNodes: [event.node], columns: [event.column.colId] });
What that does is sets the data directly in the data store used by aggrid, then you tell it to refresh that grid. That will prevent the onCellValueChanged event from having to be called again.
(if you arent using colIds you can use the field or pass the whole column, I think any of them work)

leaflet routing machine when waypoints are the same

I am using this plugin : https://github.com/perliedman/leaflet-routing-machine.
My code looks like this:
for(let i=0; i<markers.length; i++){
points.push(L.latLng(markers[i].latitude, markers[i].longitude))
}
this.routingControl = L.Routing.control({
waypoints: points
}).addTo(this.map);
When I pass points filled with different latitude/longitude, it draws the route fine. But let's imagine the following scenario. let's say that points array contains 3 items. each item contains latitude/longitude and let's say that those latitude/longitude are the same. So something like this:
34.72581233927868 -80.71105957031251
34.72581233927868 -80.71105957031251
34.72581233927868 -80.71105957031251
Now what Routing control does is as it can't draw the route, it automatically zooms in in the maximum way and in the console, it shows the errors. {"message":"Zoom level must be between 0-20."}
Workaround 1: after drawing routes, i decided to use settimeout after 1 second and there I zoom at 11 by myseslf. this way it zooms out, but in the console, errors still stay. How do I fix this?
If you know your input can be invalid, then the cleanest way would be to filter it on the way in to remove duplicates. If that's not possible for some reason and you want to let LRM determine if there's an error, then you can catch the error event with:
L.Routing.control({
...
})
.addTo(this.map)
.on('routingerror', function(e) {
// do your error action here - e.g. zoom out
})
For more information on handling errors in LRM, see https://www.liedman.net/leaflet-routing-machine/api/#eventobjects

Creating timed markers on a leaflet map?

I have a leaflet project where I am delivering a continuous stream of 'coordinates' via WebSockets to the client browser. I need to be able to display markers corresponding to those locations for a set period of time (something like 1 or 2 seconds) and then remove them (to make room for more locations).
Can anyone help me out or point me in the direction of resources wherein I could find some help?
Thanks!
Edit : Why the downvote? Its a legitimate and common question and one without a lot of solutions online.
Here is some code from the documentation (http://leafletjs.com/reference-1.0.3.html#layer):
var layer = L.Marker(latLng).addTo(map);
layer.addTo(map);
layer.remove();
So in order to remove it after 2 seconds, I think you could try this one:
var layer = L.Marker(latLng).addTo(map);
layer.addTo(map);
setTimeout(function() {
layer.remove();
}, 2000);
Example