The new Yandex.Metro interface and the technologies with which it works
Today we launched a new version of the web interface of the Yandex.Metro service . Now, in the new "island" design, subway schemes of five cities are available. But we updated not only the visual part, but also transferred all the logic from the server side to the client side.
In this post we would like to tell you exactly how we did it, what decisions we chose and why.
Yandex.Metro service was launched back in 2007. Since then, the web interface has practically not developed, the design looked frankly outdated. The venerable age of the service affected the technical side. All schemes were sets of images in GIF format, on which objects were placed using coordinates. Because of this, difficulties arose in updating the graph; it was impossible to make changes to the scheme on your own (you had to attract outsourcers and order pictures from them). Since station names were also embedded in pictures, localization of circuits would require the generation of complete sets of GIF images.
None of the developers of the server side are currently working on this service, and it is very poorly documented. So the backend of the old Yandex.Metro is a kind of black box that could take parameters and return routes. However, supporting him was extremely difficult. The service definitely required updating, and the main question was how to bring it as close as possible to the technology level of mobile applications.
This is not to say that Yandex.Metro was completely abandoned, it has a fairly wide audience. About 220 thousand people visit the web interface daily, the Android application has been downloaded 3 million times, for iOS - 1 million downloads. It is worth noting that mobile applications were developed somewhat later than the web version, and are devoid of most of its shortcomings.
When developing, it was necessary to take into account several important changes that have occurred since the launch of the Metro:
To reproduce the graph, the same data was used as for mobile applications. They had already collected a database of XML files, each of which contained one scheme and data on graph connectivity. It will also simplify localization: in the near future, all schemes will be translated into different languages.
At the very beginning of the development, the question arose about which technology to use for drawing the circuit: canvas, SVG or CSS3. A rendering method through the RaphaelJS visualization library was also considered. This library draws in SVG, but if it detects IE8, rendering takes place in VML, which ensures maximum cross-browser compatibility.
At first, three schema prototypes were written based on XML files: on canvas, SVG, and RaphaelJS. All three options were driven through benchmarks. According to the results, the SVG option turned out to be the fastest, followed by canvas with a not very significant lag, and the RaphaelJS based option was the slowest. Too much time is spent loading and executing code that checks which functions are available in the browser used. At a hundred rendering iterations, we got the following results in Chrome and Firefox, respectively:
So the final choice was between canvas and SVG. Canvas has its advantages, but SVG is better for drawing vector schemes. The fact is that SVG elements fit well in DOM pages. This means that you can refer to them as ordinary DOM-nodes with a familiar interface, to catch events, etc. In addition, in vector format, there is no need to redraw the scheme when zooming.
In order to be able to select routes in the diagram, it was required to divide it into layers. The lowest layer is intended for static objects that help you navigate. In future versions, rivers and other notable landscape elements will be displayed there. The whole scheme is drawn on the middle layer. The topmost layer is the route layer. To be able to select it, there is one more layer between it and the layer of the general scheme: by connecting it, we ensure the blurriness of the general scheme.
Later, for the convenience of developing and assigning CSS styles, the layer with the general scheme was divided into three: with stages, stations and transitions.
The new scheme has undeniable advantages: it is vectorial, it can be scaled without writing additional code, it is easier to maintain and add additional features. Unfortunately, changes to the schema are still made manually through XML files, but even this is easier than in the original version.
All the routing logic in the old version of the Metro was executed on the server side. For each user action, a request was sent to the server, a new option was calculated there and a response was sent. For calculations, the Dijkstra algorithm was used with small modifications to support the search for several routes, for example, you can chop off the last edge and continue searching for paths.
About the same algorithm was used to create mobile applications, but since they were developed a little later, they used more modern technologies and approaches. There, all the logic has already been transferred to the client part. In the iOS version, the graph is presented as an adjacency matrix, and the object-oriented model is implemented in the Android client. Stations are presented in the form of JS-objects, each of which has an array of links - hauls. These compounds are also objects.
When choosing a framework for the web interface, both options were considered. But the model from the application for Android seemed to us more optimal.
For each request, the router returns about 30 routes. Very few of them are relevant - not more than 10%. But the problem is that many users have a different understanding about the “optimal route”. Some prefer speed and some prefer comfort. A girl in high heels will be ready to spend the extra 10 minutes so as not to go along the long passage, but for a person who is late for the plane, this difference will be critical. So, the routes need to be evaluated according to different criteria.
In order not to complicate the whole system, special filters were developed: a class of objects whose interface consists of one method that performs one simple action. Composite filters containing many filters have also been added. They are applied to the route array in the order specified by the developer.
Now filtering is moved to a separate module. This will allow us in the near future to unify the filtering process on all platforms. Thus, the choice of route will not depend on which device the request is made from.
When developing MVC, we took advantage of the existing practices in Yandex: JS libraries responsible for island design, logic, data storage and notification of components about user actions and state changes.
Immediately after launching a new version of the service, we plan to bring our scheme in line with the official one. Next, we will introduce some features that are already in mobile applications. For example, information on which cars it is most convenient to make a transplant from. We also implement features already built into the architecture: the ability to select routes and translate schemes into several languages. In addition, plans to develop a visual tool for making changes to the scheme. So far, any changes have been made by editing the xml files. If a visual editor appears, one content manager will be able to quickly make changes simultaneously for all platforms.
Yandex.Metro service was launched back in 2007. Since then, the web interface has practically not developed, the design looked frankly outdated. The venerable age of the service affected the technical side. All schemes were sets of images in GIF format, on which objects were placed using coordinates. Because of this, difficulties arose in updating the graph; it was impossible to make changes to the scheme on your own (you had to attract outsourcers and order pictures from them). Since station names were also embedded in pictures, localization of circuits would require the generation of complete sets of GIF images.
None of the developers of the server side are currently working on this service, and it is very poorly documented. So the backend of the old Yandex.Metro is a kind of black box that could take parameters and return routes. However, supporting him was extremely difficult. The service definitely required updating, and the main question was how to bring it as close as possible to the technology level of mobile applications.
This is not to say that Yandex.Metro was completely abandoned, it has a fairly wide audience. About 220 thousand people visit the web interface daily, the Android application has been downloaded 3 million times, for iOS - 1 million downloads. It is worth noting that mobile applications were developed somewhat later than the web version, and are devoid of most of its shortcomings.
When developing, it was necessary to take into account several important changes that have occurred since the launch of the Metro:
- Visualization technologies in browsers have stepped far forward, rendering the map on the client side is no longer a problem.
- The performance of JavaScript engines has greatly improved. Many tasks that previously could only be done on the server are now fully feasible on the client side.
- The proportion of outdated browsers has decreased. IE6 and IE8 almost never occur, which means there is no reason to use GIF.
How was the new version developed?
To reproduce the graph, the same data was used as for mobile applications. They had already collected a database of XML files, each of which contained one scheme and data on graph connectivity. It will also simplify localization: in the near future, all schemes will be translated into different languages.
At the very beginning of the development, the question arose about which technology to use for drawing the circuit: canvas, SVG or CSS3. A rendering method through the RaphaelJS visualization library was also considered. This library draws in SVG, but if it detects IE8, rendering takes place in VML, which ensures maximum cross-browser compatibility.
At first, three schema prototypes were written based on XML files: on canvas, SVG, and RaphaelJS. All three options were driven through benchmarks. According to the results, the SVG option turned out to be the fastest, followed by canvas with a not very significant lag, and the RaphaelJS based option was the slowest. Too much time is spent loading and executing code that checks which functions are available in the browser used. At a hundred rendering iterations, we got the following results in Chrome and Firefox, respectively:
technology | time_to_download | time_to_draw | overall_time |
canvas | 34.58 ms | 181.72 ms | 216.30 ms |
svg | 28.02 ms | 74.02 ms | 102.07 ms |
svg-raphael | 45.87 ms | 1147.58 ms | 1193.45 ms |
technology | time_to_download | time_to_draw | overall_time |
canvas | 55.26 ms | 769.16 ms | 824.42 ms |
svg | 102.60 ms | 136.51 ms | 239.11 ms |
svg-raphael | 148.40 ms | 2369.88 ms | 2518.28 ms |
So the final choice was between canvas and SVG. Canvas has its advantages, but SVG is better for drawing vector schemes. The fact is that SVG elements fit well in DOM pages. This means that you can refer to them as ordinary DOM-nodes with a familiar interface, to catch events, etc. In addition, in vector format, there is no need to redraw the scheme when zooming.
In order to be able to select routes in the diagram, it was required to divide it into layers. The lowest layer is intended for static objects that help you navigate. In future versions, rivers and other notable landscape elements will be displayed there. The whole scheme is drawn on the middle layer. The topmost layer is the route layer. To be able to select it, there is one more layer between it and the layer of the general scheme: by connecting it, we ensure the blurriness of the general scheme.
Later, for the convenience of developing and assigning CSS styles, the layer with the general scheme was divided into three: with stages, stations and transitions.
The new scheme has undeniable advantages: it is vectorial, it can be scaled without writing additional code, it is easier to maintain and add additional features. Unfortunately, changes to the schema are still made manually through XML files, but even this is easier than in the original version.
Search for paths on a graph
All the routing logic in the old version of the Metro was executed on the server side. For each user action, a request was sent to the server, a new option was calculated there and a response was sent. For calculations, the Dijkstra algorithm was used with small modifications to support the search for several routes, for example, you can chop off the last edge and continue searching for paths.
About the same algorithm was used to create mobile applications, but since they were developed a little later, they used more modern technologies and approaches. There, all the logic has already been transferred to the client part. In the iOS version, the graph is presented as an adjacency matrix, and the object-oriented model is implemented in the Android client. Stations are presented in the form of JS-objects, each of which has an array of links - hauls. These compounds are also objects.
When choosing a framework for the web interface, both options were considered. But the model from the application for Android seemed to us more optimal.
Search Filtering
For each request, the router returns about 30 routes. Very few of them are relevant - not more than 10%. But the problem is that many users have a different understanding about the “optimal route”. Some prefer speed and some prefer comfort. A girl in high heels will be ready to spend the extra 10 minutes so as not to go along the long passage, but for a person who is late for the plane, this difference will be critical. So, the routes need to be evaluated according to different criteria.
In order not to complicate the whole system, special filters were developed: a class of objects whose interface consists of one method that performs one simple action. Composite filters containing many filters have also been added. They are applied to the route array in the order specified by the developer.
Now filtering is moved to a separate module. This will allow us in the near future to unify the filtering process on all platforms. Thus, the choice of route will not depend on which device the request is made from.
Client Application Architecture
When developing MVC, we took advantage of the existing practices in Yandex: JS libraries responsible for island design, logic, data storage and notification of components about user actions and state changes.
Plans
Immediately after launching a new version of the service, we plan to bring our scheme in line with the official one. Next, we will introduce some features that are already in mobile applications. For example, information on which cars it is most convenient to make a transplant from. We also implement features already built into the architecture: the ability to select routes and translate schemes into several languages. In addition, plans to develop a visual tool for making changes to the scheme. So far, any changes have been made by editing the xml files. If a visual editor appears, one content manager will be able to quickly make changes simultaneously for all platforms.