The first two pictures, the rest is all blowing

The realization of real-time movement and track tracking of multiple vehicles based on VUE + Baidu map is divided into two parts, namely, the first part “Heart Course” and the second part “God View”. The first part is the background introduction and the dettour I took in the process of realization, and the second part is the final implementation scheme. There is no Such thing as a God perspective, but hopefully one day we can make fewer detours by constantly replaying.

This is the first part, the Journey of the Mind.

Project background

Leave out ten thousand words and give them to the product manager. The demand that CAME to me was to actually move cars in real time and track them. No, I actually got a code for a teammate running away! Please ignore the floating layer in the lower right corner of figure 2 when I recorded the screen.

Old code ideas

I took a look at the code of this unknown teammate. In fact, his original code structure is relatively clear.

Designs that can be retained:

  1. Data is received in Websocket mode. Because the vehicle data will be continuously generated, the front-end timing pull is not desirable, so we use websocket to receive data.
  2. Marker and label a vehicle by id for later deletion
  3. Well, I’m trying to think about it. There’s got to be a 3

Points for improvement

  1. The vehicle is bouncing forward, very unreal
  2. The background is too plain

Features that need to be added

  1. The vehicle needs to display real-time speed
  2. You need to add a list that updates the current running vehicles in real time, limiting the display to the top 10 vehicles
  3. Add track line function to track the vehicle’s forward track in real time

The cause of the pulsing of the vehicle

The original implementation idea is: every time the data is received, the car draws the cover (car and label), and redraws the cover with the new latitude and longitude data, the car directly from the first point to another point, so it looks like jumping over.

Reform steps

Here are some of the ideas that were eventually abandoned during the project. If you want to see the final project directly, please go to God’s Perspective

1. Redraw = “set new position

My first reaction was that it was always faster to move positions than to draw new ones, so I started with data structure transformation, recorded the track messages sent from the back end according to vehicle classification, set the initial state as ‘undraw’, and moved all the points in the state of ‘undraw’ every time a new message came. Set the state to ‘drawing’ before the move starts and ‘drawed’ after the move (here a pit is first buried, pit 1).

I thought it looked perfect, and found that when setting the position of the vehicle, it often indicated that the covering of the vehicle did not exist yet. But I clearly draw the car and then move its position ah, can draw the car this step is asynchronous? (AT this point, I also foolishly neglected the big coordinate conversion function, Sigh)

Make it asynchronous, and add two more states! If it is the first point of the car, it will be marked’ marking’ before the drawing, marked’ marked’ after the drawing, and only after the state of the first point is marked’ marked’ will subsequent moves be made. Finally no error, but I was expecting nothing at all, which seems worse. (Continue to bury, pit 2)

2. Smoothing effect

Start a full web search how to make the vehicle smooth movement:

A) Baidu Map has its own trajectory animation API reference 4, which is more suitable for knowing the entire trajectory and playing it back within a specified time.

B) Reference 2 for the multi-icon smooth movement scheme based on Baidu Map written by the predecessors. The main idea is to fill points. Calculate the number of points to be filled according to the distance between two points, and use setInterval to move to the next point regularly. However, the function used to calculate the distance is applicable to Baidu Map JSAPI V2 version, we use Baidu Map JS WebGL V1 version, it should be noted that it can not be directly used.

C) The other ideas are basically complementary points, that is, the functions to calculate distance are not quite the same, for example, reference 3, TURfJS package learned from other articles, there are a lot of map and distance related tool functions, here to record, may be used in the future.

Here is the basic idea of using the complement. At first, since plan B) could not be used directly, I adopted the function in Plan C), and found that the vehicle hardly moved (in fact, the vehicle had already flown away, so I was young and ignorant and thought that the vehicle had not moved). So I printed out the distance and saw that the distance between the two points was 0.00099km. I wondered if it was too close, so it seemed not to move. Or the distance of this library is not allowed to calculate, deathless heart ran to Baidu there to try, although big so a throw throw, but absolute value is still very small.

var from = turf.point([113.27720709322817.23.351992192427748]);
var to = turf.point([113.2772194870973.23.352001006312186]);
var options = {units: 'miles'};

turf.distance(from, to, options);
0.0009944611081986045
Copy the code

At this time to take a clever first, not calculate the distance, their first fixed a segmentation of the point to see the effect. Looking at the messages sent from the back end, about 2 pieces of data per second for each vehicle, based on 60fps, set 30 points between the two points to draw, and then tried it confidently.

Then found an exception, move, jump jump from one into a meal, then blind I finally find that the ordinary baidu coordinate transformation function, it not only to be called baidu map API to get the converted result, and every time limits the maximum 10 points, while the back of my house in order to let my work on time, It gets stuck at night, so the problem is magnified. I fumbled around for a long time, but the bottleneck was not in drawing, but in calling the API. The problem with pit 2 is explained. The reason for asynchrony is not drawing, but calling the API! Let’s make do with 10 points, it’s better than nothing…

3. In the skin

As time went by, I was still anxious in my heart, thinking that the stupid thing I changed could not be delivered. In order to deceive myself and the product, I planned to make some improvement 2 and change the background. At this time, I did not know that this technical term is called satellite map, and I searched the whole network and found the relevant Settings, in fact, it is a matter of one sentence.

bMap.setMapType(BMAP_EARTH_MAP)

Well, that looks like two days ‘work. [Think of the browser to change the core, manual dog head]

4. Simulate websocket data locally

It was already Friday afternoon, and according to my observation two days ago, the back-end service would be turned off at night. How could this satisfy my desire to work overtime on weekends? Just send a number. I can do that, too.

Step 1: Using the scheme of Reference 1, collect a piece of real-time data and save it into HAR file.

Step 2: search how to open HAR file and find that it is json in nature, that’s easy to do, change the suffix to JSON and observe the data.

Step 3: Use websocket keyword search, found the _webSocketMessages field and only one, the data I care about is in it.

Step 4: Extract all received data, that is, data of type ‘receive’, and save it as a JSON file.

const fs = require('fs')
const path = require('path')

const input = process.argv[2]
const fullname = input.split('/').slice(-1) [0]
const filename = fullname.substring(0, fullname.lastIndexOf('. ')) 
let objArr = JSON.parse(fs.readFileSync(input, 'utf8')).log.entries

const websocketReqs = objArr.filter(o= > { return o._webSocketMessages && o._webSocketMessages.length > 0})
const receivedMsgs = websocketReqs.length > 0 && websocketReqs[0]._webSocketMessages.filter(item= > item.type === 'receive')

if (receivedMsgs && receivedMsgs.length > 0) {
    try {
        fs.writeFileSync(path.resolve(__dirname, `. /${filename}.json`), JSON.stringify(receivedMsgs, null."\t"))}catch(e) {
        console.log(e)
    }
}
Copy the code

The format of the JSON file is as follows:

[{"type": "receive"."time": 1648170807.1585."opcode": 1."data": "realdata1"
	},
	{
		"type": "receive"."time": 1648170807.329674."opcode": 1."data": "realdata2"}]Copy the code

Step 5: Start a simplest Websocket back-end service with NodeJS, read the JSON file, and perform data playback at intervals according to the time field in the data.

const realtimeTraces = JSON.parse(fs.readFileSync('./ Extracted jons file. json'.'utf8'));
const server = ws.createServer((connect) = > {
  console.log(The user link came up);
  // When the user passes in the data, the text event is emitted
  connect.on("text".(data) = > {
    console.log('Data from users${data}`);
  });
  // When the connection is disconnected, this event is executed
  connect.on("close".() = > {
    console.log(The link is broken);
  });

  // Register an error event to handle user error messages
  connect.on("error".() = > {
    console.log('Abnormal user link');
  });

// send realtime trace
let baseTime
for(let i=0; i<realtimeTraces.length; i++) {
    const item = realtimeTraces[i]
    if (i === 0) {
        baseTime = item.time
    }
    setTimeout(() = > {
        connect.send(typeof item.data === 'string' ? item.data : JSON.stringify(item.data));
      }, (item.time - baseTime)*1000); }});const PORT = 7777;
server.listen(PORT, () = > {
  console.log('Service started successfully, port number${PORT}`);
});
Copy the code

5. Modify the drawing trigger timing

With the back-end data ready, you can happily explore the front-end implementation again. (As I played back this data over and over again, I thought of the loop in The Beginning…)

At this time, I found a strange phenomenon, the car suddenly spread all over the screen, like the feeling of Windows poisoning (accidentally exposed age). Being a tryptophobic person, I immediately closed the page and thought about life and, no, why.

The logic used to be to trigger a drawing (both new and move) every time a message was received, but if a large number of messages flooded in at once, the screen would be filled with cars (later this problem was magnified by 1000 times because I forgot to multiply setTimeout by 1000 when distributing data…). .

Since the speed at which messages are received does not match the speed at which messages are consumed, a message queue should be created. Oh, yes, I am the front end, there should be an array, when receiving the message, the car information cache, then look for an opportunity to consume the message (pit 3, pit 3).

HengChi HengChi changed, write the receive messages and consumption function respectively, consumption news there will iterate over all cars, car and according to the state of drawing into the circulation, and then to find my pain I can’t find a suitable for the first driving force (god will not help me), hack again, in the car when you receive the message of meter number, when the number of cars is 1, Trigger a consumption message as an entry point to the function call.

At any rate, the car moved.

6. Vehicles are removed from the map after they disappear

In less than two seconds, the car filled the screen again.

Oh, shit, I just keep adding cars, but I don’t remove them when they disappear.

So the question is, how do you determine when a vehicle disappears?

After observing the back-end data, I found that many messages were empty, so I took a board by myself. If the car still did not come after N messages, I judged that it had disappeared. But when I kept turning N up and it was still bad, I had to admit it wasn’t working. And then I came up with a really bad idea, because we’re on the highway, and we’re looking at a very short stretch of road, so let’s say first in, first out, and when we pass N, we remove the first car.

Well, after a bit of manipulation, I can finally look at the screen.

In fact, I was in a panic, already in the code can run completely fatalistic state.

7. Ask the boss for help

When I got off work on Friday, I honestly reported the work to the team leader, feeling that the project was going to be out of control. The first reaction of the normal front end was to ask the back end to process the data for me, and I was only responsible for the display. To show 10 pieces of data, let the back end return 10 pieces of data, and I don’t have to determine if the car disappeared or cache all the information myself. In short, it doesn’t matter what the previous message format is, as long as I think about what data I want, mock the good data to do a good demo, let the backend to adapt it.

I thought it was the big guy, the way of thinking is different; I was skeptical, thinking I didn’t know when the car disappeared, and the back end had the same data as me, so how would he know.

8. The DIFF algorithm

Anyway, let’s do it the way the boss wants. First simulated the first 10 vehicle data message, happily received. When the second message was simulated, it was found that the front end could not be replaced all at once because the speed of the data did not match the speed of the drawing. Assuming direct replacement, the unfinished car would never have a chance to be drawn again.

Can make all the simple problems complicated I still do not give up at this time, thought of the famous diff algorithm, according to the two messages before and after diff, but also seriously write a good annotation.

/ / the old and new contrast list, according to the different situation with different operating / / if the new, the old have no, is inserted, the tag to 'PLACEMENT' / / if the old, new, is deleted, the tag to 'DELETION / / if both, is updated, the tag for the' UPDATE 'Copy the code

When I finished tag all the data to execute, I encountered the previous problem again. The premise of moving the vehicle was to create the vehicle, but the creation of the vehicle was asynchronous, I still needed to record the status, so I could not use the data directly returned by the back end, and this scheme failed.

9. Simplify things from a car’s point of view

When I think about all the cars together, it’s hard to pinpoint the problem. That’s when I realized why I didn’t start with a car.

For a car, the requirements are clear:

  1. Receives the message
  2. Draw the car
  3. Mobile car
  4. Remove the car

It suddenly became clear that each step could be done independently.

  1. The backend receives messages when sending messages.
  2. When receiving the message, determine whether it is a new car, if it is a new car, draw the car.
  3. After drawing the car, if there are still undrawn points, move the car. Take two points at a time as the starting and ending points and move them through the complement algorithm.
  4. If there are no undrawn points (preset 3 seconds), the vehicle is judged to have disappeared and removed. This is done by recounting every time you draw a new point.

Suddenly, there is no need to think about the first propulsion problem (pit 3), no need to add ‘marking’ and ‘marked’ states to the first point in order to know when the car can be moved, and the vehicle disappearance algorithm is no longer Schrodinger’s cat. It can be dynamically adjusted according to the actual data frequency collected. In addition, the original operation was to take a batch of points (pit 1), now take two points at a time, the performance is more stable.

By now, most of the detours have been completed, and the realization of real-time movement and track tracking of multiple vehicles based on Vue + Baidu map (God Perspective) opens the real technology sharing.

reference

1. Save and view web requests using Chrome

2. Baidu multi icon smooth movement

3. Smooth movement of multiple line segments

4. Trajectory animation