I have an application that implements the movement of animation characters along a previously laid route on a city map. Routes, more precisely, the path made manually in the vector editor. All route creation techniques are shown here .

enter image description here

Below is the code that implements this technique and animation:

 .container { width:100%; height:100%; } 
 <div class="container"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1" viewBox="0 0 800 540" > <defs> <path id="walk" d="m343 268 34-10 50-9-33-86 22-14 7-21 8-3 13 18 34-25 47 65 22-19" /> <g id="Man" transform="translate(0,0) scale(1,-1)"> <path fill="none"> <animate attributeName="d" begin="0.1s" dur="0.3s" repeatCount="indefinite" values="M-3,0 0,10 3,0 M0,10 0,16 l 4,-5 M0,16 l-4,-5 M0,16 c4,4 -4,4 0,0; M 0,0 0,10 0,0 M0,10 0,16 l 0,-5 M0,16 l 0,-5 M0,16 c4,4 -4,4 0,0; M-3,0 0,10 3,0 M0,10 0,16 l 4,-5 M0,16 l-4,-5 M0,16 c4,4 -4,4 0,0"/> </path> </g> </defs> <image xlink:href="https://i.stack.imgur.com/XPdWW.png" width="100%" height="100%" /> <path id="train" stroke-dasharray="312" stroke-dashoffset="312" stroke-width="2" d="M443 534 426 477 415 435 397 391 375 347 350 304 334 277 317 251" style="fill:none;stroke:violet;"/> <text font-size="28" font-family="Times New Roman" fill="#517DA6" > <textPath id="result" xlink:href="#train"> <tspan dx="0" > &#128642; </tspan> <tspan dx="-12"> &#45; </tspan> <tspan dx="-15"> &#128643;</tspan> <tspan dx="-12"> &#45;</tspan> <tspan dx="-15"> &#128643; </tspan> <tspan dx="-12"> &#45; </tspan> <tspan dx="-15"> &#128643;</tspan> <tspan dx="-12"> &#45;</tspan> <tspan dx="-15"> &#128643; </tspan> <tspan dx="-12"> &#45; </tspan> <tspan dx="-15"> &#128643;</tspan> <tspan dx="-12"> &#45;</tspan> <tspan dx="-15" > &#128642; </tspan> <animate id="anTrain" begin="0s;an5.end" dur="12s" repeatCount="1" attributeName="startOffset" values="-60%;45%;45%;-60%" fill="freeze"/> </textPath> </text> <path id="walk" stroke-dasharray="409" stroke-dashoffset="409" stroke-width="3" d="m343 268 34-10 50-9-33-86 22-14 7-21 8-3 13 18 34-25 47 65 22-19" style="fill:none;stroke:#B34EE9"> <animate id="anPathWalk" attributeName="stroke-dashoffset" begin="anTrain.end-7.5s" dur="2s" values="409;0" fill="freeze" /> </path> <use xlink:href="#Man" transform="translate(0,0) scale(1.2)" style="stroke:blue; fill:black;"> <animateMotion id="an2" begin="anPathWalk.end" dur="16s" repeatCount="1" > <mpath xlink:href="#walk"/> </animateMotion> </use> <use xlink:href="#Man" transform="translate(0,0) scale(1.2)" style="stroke:crimson;" > <animateMotion id="an3" begin="anPathWalk.end+0.5s" dur="17s" repeatCount="1" > <mpath xlink:href="#walk"/> </animateMotion> </use> <use xlink:href="#Man" transform="translate(0,0) scale(1)" style="stroke:black;"> <animateMotion id="an4" begin="anPathWalk.end+1s" dur="13s" repeatCount="1" > <mpath xlink:href="#walk"/> </animateMotion> </use> <use xlink:href="#Man" transform="translate(0,0) scale(0.8)" style="stroke:red; fill:black;"> <animateMotion id="an5" begin="anPathWalk.end+1.5s" dur="11s" repeatCount="1" > <mpath xlink:href="#walk"/> </animateMotion> </use> <use xlink:href="#Man" transform="translate(0,0) scale(0.8)" style="stroke:black;"> <animateMotion id="an5" begin="anPathWalk.end+1.8s" dur="9.5s" repeatCount="1" > <mpath xlink:href="#walk"/> </animateMotion> </use> </svg> <audio src="https://svg-art.ru/files/Time_Machine.mp3" autoplay="autoplay"></audio> </div> 

Is it possible to maximally automate the laying of a route from one point of the map to another by creating broken segments of the path clicking the control points of the map, - at the branching of the streets, while changing the direction of movement.

In other words, - the work of the program should repeat the work in the vector editor, when we put the nodal points that are automatically connected by line segments, and at the output we get a ready-made patch formula.

There is a topic in which the laying of routes is implemented, but it requires manually specifying the coordinates of the vertices of broken lines.

    2 answers 2

    The first time I work with Yandex maps, I was unpleasantly surprised that they only support the integer value of the zoom, this complicates the synchronization of the map with the svg image, in which the zoom can be fractional.

    The solution is made with d3.js, first you need to position the map with the mouse, then click the button add route and the svg overlay will appear, which you can click on while plotting the route. In this mode, dragging with the right mouse button drags the map, clicks of the left one add points to the route, doubles by points delete them. In order to get the SVG result in the form of SVG code, click on the button get code


    Here's what it looks like:

    enter image description here


    PS: I accept offers on route animation and other offers.

     <script src="https://api-maps.yandex.ru/2.1/?lang=ru_RU"></script> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.7.2/css/all.min.css" rel="stylesheet"/> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script> <style> body, #map, #overlay, svg, #bg { position: absolute; margin: 0; width: 100vw; height: 100vh; overflow: hidden; } #bg { top: 0; left: 0; background-color: rgba(0,0,0,0.5); pointer-events: all; } button, textarea { pointer-events: all; } textarea { position: absolute; width: 400px; height: 300px; left: 50%; top: 50%; transform: translate(-50%,-50%) } #ui { pointer-events: none; position: absolute; padding: 5px; } path { fill: none; stroke-width: 2.2; stroke: red; } circle { pointer-events: all; stroke: red; stroke-width: 1.5px; fill: #fff; fill-opacity: .2; cursor: move; } .selected { fill: #ff7f0e; stroke: #ff7f0e; } .hidden { display: none !important; } .buttons { position: absolute; width: 150px; } button { display: inline; } </style> <div id="map"></div> <div id="overlay" class='hidden'> <svg></svg> </div> <div id="ui"> <div id="bg" class='hidden'> <textarea></textarea> </div> <div class="buttons"> <button class='fa fa-2x fa-code'></button> <button class='fa fa-2x fa-route'></button> </div> </div> <script> let lat = 60; let lon = 30.3; let zoom = 15; let yMaps; let points = []; let transform = {}; let dragged = null; let selected = points[points.length-1]; let line = d3.line().curve(d3.curveLinear); let svg = d3.select("svg"); let canvas = svg.append('g'); let path = canvas.append("path") .datum(points); svg.on("mousedown", mousedown) .on("mousemove", mousemove) d3.select(window) .on("mouseup", mouseup) .on("resize", adjustSize); d3.select('.fa-route').on('click', showOverlay); d3.select('.fa-code').on('click', showCode); window.oncontextmenu = () => false; ymaps.ready(startmap); svg.call(createZoom()); adjustSize(); redraw(); function showCode() { toggleElement('#bg'); let div = d3.select(document.createElement('div')) .html(svg.node().outerHTML) div.select('svg') .attr('zoom', yMaps.getZoom()) .attr('lat', yMaps.getCenter()[0]) .attr('lon', yMaps.getCenter()[1]); div.selectAll('circle').remove(); d3.select('#bg textarea').html(div.node().innerHTML); } function showOverlay() { lat = yMaps.getCenter()[0]; lon = yMaps.getCenter()[1]; zoom = yMaps.getZoom(); d3.select(this).classed('hidden', true); toggleElement('#overlay', false); } function toggleElement(selector, isVisible) { return d3.select(selector) .node() .classList .toggle('hidden', isVisible) } function applyTransform() { transform = d3.event.transform; canvas.attr("transform", transform); onTransform(transform); } function createZoom() { return d3.zoom() .filter(() => d3.event.button === 2) .scaleExtent([1, 1]) .on("zoom", applyTransform); } function adjustSize() { let w = window.innerWidth; let h = window.innerHeight; svg.attr("width", w).attr("height", h) .attr("viewBox", `${-w/2} ${-h/2} ${w} ${h}`); } function redraw() { canvas.select("path").attr("d", line); var circle = canvas.selectAll("circle.knob") .data(points, d => d); circle.exit().remove(); let newNodes = circle.enter() .append("circle") .classed('knob', true) .attr("r", 1e-6) .on("mousedown", d => { selected = dragged = d; redraw(); }) .on("dblclick", deletePoint) .transition() .duration(250) .attr("r", 6.5); circle.merge(newNodes) .classed("selected", d => d === selected) .attr("cx", d => d[0]) .attr("cy", d => d[1]); if (d3.event) { d3.event.preventDefault(); d3.event.stopPropagation(); } } function mousedown() { if (d3.event.button !== 0) return; points.push(selected = dragged = d3.mouse(canvas.node())); redraw(); } function mousemove() { if (!dragged) return; let m = d3.mouse(canvas.node()); dragged[0] = m[0]; dragged[1] = m[1]; redraw(); } function mouseup() { if (!dragged) return; mousemove(); dragged = null; } function deletePoint(d) { if (!selected) return; let i = points.indexOf(selected); points.splice(i, 1); selected = points.length ? points[i > 0 ? i - 1 : 0] : null; redraw(); } function startmap() { yMaps = new ymaps.Map("map", { center: [lat, lon], zoom: 15, controls: [] }); } function onTransform(transform) { var merc = Math.cos(yMaps.getCenter()[0]*Math.PI/180); var z = zoom;// + Math.log2(transform.k); var s = Math.pow(2, z-1) * 256 / 180 / merc; yMaps.setCenter([ lat + transform.y / s, lon - transform.x / s / merc, ]); // yMaps.setZoom(z+1); } </script> 

    PS Cards may not be Yandex

    • that's great ... good implementation ... :) a plus sign from me - MaximLensky
    • @Stranger in the Q is impressive! But there is no output of the patch formula, or I did not understand how this is done (+) - Alexandr_TT
    • @ Alexandr_TTzadumka is somewhat different, this web application will be able to create and open a url, parse it and show the animation, the fact is that just the path is not enough, you need to georeference, in fact it is path + latitude + longitude + zoom - Stranger in the Q
    • @Stranger in the Q Great, but that's another story for a cooler app. The question clearly states - you need to output the formula of the patch, which can be copied and used in the animation, such as routes to the office. It is not necessary to do this on an interactive map, you can follow the link like me to route the screenshot of the map. But the patch is definitely needed. Simply laying routes is a standard feature of Yandex maps. - Alexandr_TT
    • one
      @Alexandr_TT No questions, I'll do textarea - Stranger in the Q

    There are a lot of information, screenshots, snippets, so I decided to issue a separate answer, since this volume will not fit in the comments.

    I want to dwell on some aspects of the application of the wonderful solution @Stranger in the Q

    As I tried to apply this solution:

    • I select the necessary fragment on the map and make a screenshot.

    enter image description here

    • I add anchor points along the selected route and display the code for this route.

    enter image description here

    I copy the code into a separate file, in which a screenshot of the map is added using the <image> command

     <div class="container"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1400" height="858" viewBox="-700 -429 1400 858" zoom="14" lat="60.02023833197165" lon="30.424723377333542"> <image xlink:href="https://i.stack.imgur.com/xKUKV.png" width="100%" height="100%" /> <path stroke="red" fill="none" d="M-669,34L-434,-160L-218,-47L-202,-50L-199,-40L-194,-28L-147,-7L1,67L99,116L96,75L93,-5L88,-89L86,-158L86,-297L-47,-352L-161,-130L-63,-71L-9,-173"></path> </svg> </div> 

    • The map has shifted to the right and down due to negative values viewBox="-700 -429 1400 858"
    • Zero viewBox="0 0 1400 858"

     <div class="container"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1400" height="858" viewBox="0 0 1400 858" zoom="14" lat="60.02023833197165" lon="30.424723377333542"> <image xlink:href="https://i.stack.imgur.com/xKUKV.png" width="100%" height="100%" /> <path stroke="red" fill="none" d="M-669,34L-434,-160L-218,-47L-202,-50L-199,-40L-194,-28L-147,-7L1,67L99,116L96,75L93,-5L88,-89L86,-158L86,-297L-47,-352L-161,-130L-63,-71L-9,-173"></path> </svg> </div> 

    • The map fell into place, but went left and up the route path

    To return the Path to its place, I add the command to <path transform="translate(700 429)"

     <div class="container"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1400" height="858" viewBox="0 0 1400 858" zoom="14" lat="60.02023833197165" lon="30.424723377333542"> <image xlink:href="https://i.stack.imgur.com/xKUKV.png" width="100%" height="100%" /> <path transform="translate(700 429)" d="M-669,34L-434,-160L-218,-47L-202,-50L-199,-40L-194,-28L-147,-7L1,67L99,116L96,75L93,-5L88,-89L86,-158L86,-297L-47,-352L-161,-130L-63,-71L-9,-173" stroke="red" stroke-width="2" fill="none"></path> </svg> </div> 

    Findings:

    To get the initial position of the route, you need to make the following changes in the code that the program displays

    1. Replace negative values ​​with viewBox="-700 -429 1400 858"
      to zero viewBox="0 0 1400 858"

    2. Add the command to <path transform="translate(700 429)"

    Animation example

    on the route received from the @Stranger in the Q program

     <div class="container"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1400" height="858" viewBox="0 0 1400 858" zoom="14" lat="60.02023833197165" lon="30.424723377333542"> <defs> <path transform="translate(700 429)" d="M-669,34L-434,-160L-218,-47L-202,-50L-199,-40L-194,-28L-147,-7L1,67L99,116L96,75L93,-5L88,-89L86,-158L86,-297L-47,-352L-161,-130L-63,-71L-9,-173" stroke="red" stroke-width="2" fill="none"> </path> <g id="Man" transform="translate(700 429) scale(1.5,-1.5)"> <path fill="none"> <animate attributeName="d" begin="0.1s" dur="0.3s" repeatCount="indefinite" values="M-3,0 0,10 3,0 M0,10 0,16 l 4,-5 M0,16 l-4,-5 M0,16 c4,4 -4,4 0,0; M 0,0 0,10 0,0 M0,10 0,16 l 0,-5 M0,16 l 0,-5 M0,16 c4,4 -4,4 0,0; M-3,0 0,10 3,0 M0,10 0,16 l 4,-5 M0,16 l-4,-5 M0,16 c4,4 -4,4 0,0"/> </path> </g> </defs> <image xlink:href="https://i.stack.imgur.com/xKUKV.png" width="100%" height="100%" /> <path id="walk" transform="translate(700 429)" d="M-669,34L-434,-160L-218,-47L-202,-50L-199,-40L-194,-28L-147,-7L1,67L99,116L96,75L93,-5L88,-89L86,-158L86,-297L-47,-352L-161,-130L-63,-71L-9,-173" stroke="red" stroke-width="2" fill="none"> </path> <use xlink:href="#Man" style="stroke:blue; fill:none;"> <animateMotion id="an1" begin="0s" dur="20s" repeatCount="indefinite" > <mpath xlink:href="#walk"/> </animateMotion> </use> <use xlink:href="#Man" style="stroke:purple; fill:none;"> <animateMotion id="an2" begin="an1.begin+2s" dur="19s" repeatCount="indefinite" > <mpath xlink:href="#walk"/> </animateMotion> </use> <use xlink:href="#Man" style="stroke:green; fill:none;"> <animateMotion id="an3" begin="an2.begin+1s" dur="18s" repeatCount="indefinite" > <mpath xlink:href="#walk"/> </animateMotion> </use> </svg> </div> 

    • The negative viewbox is very convenient, much more convenient than the standard one. because in polar coordinates it is much easier to count relative to the center (0,0), I always do this way, then for this point (center), geo snapping is performed, if you bind at one of the corners it is more difficult to synchronize with the map, since it is also characterized by the center - Stranger in the Q
    • it may be worth making a button on the output that generates a picture with the Image inside, yes it is - Stranger in the Q
    • @Stranger in the Q I don’t insist, and you did so a lot. I just wrote - "If possible," so that other people will not get confused when they use your program. - Alexandr_TT
    • so this is an opportunity, I just deliberately made 0.0 center, and I wanted to argue in favor of this =) - Stranger in the Q