{"id":"ed78abaa85ee5aac","slug":"kinematics-of-reverse-angle-parking","trashed":false,"description":"","likes":39,"publish_level":"live","forks":3,"fork_of":null,"has_importers":false,"update_time":"2023-04-03T15:39:21.954Z","first_public_version":637,"paused_version":null,"publish_time":"2023-04-03T15:39:29.156Z","publish_version":637,"latest_version":637,"thumbnail":"7e8bd39fc91afde9e24564aeb0dd0a8c89a2cc48a44c15230402f2b6bfb3acfd","default_thumbnail":"7e8bd39fc91afde9e24564aeb0dd0a8c89a2cc48a44c15230402f2b6bfb3acfd","roles":[],"sharing":null,"owner":{"id":"78f29d059906819a","avatar_url":"https://avatars.observableusercontent.com/avatar/02cfd655debb6fc593dfe4bcacd9520f73e6b33cedef382248d08ecc40822a1b","login":"mourner","name":"Volodymyr Agafonkin","bio":"Engineer at Mapbox, creator of Leaflet, algorithms geek, open source enthusiast, musician, father of twin girls, Ukrainian. 🇺🇦","home_url":"https://agafonkin.com","type":"team","tier":"starter_2024"},"creator":{"id":"3954cddf6608132f","avatar_url":"https://avatars.observableusercontent.com/avatar/02cfd655debb6fc593dfe4bcacd9520f73e6b33cedef382248d08ecc40822a1b","login":"mourner","name":"Volodymyr Agafonkin","bio":"Engineer at Mapbox, creator of Leaflet, algorithms geek, open source enthusiast, musician, father of twin girls, Ukrainian. 🇺🇦","home_url":"https://agafonkin.com","tier":"pro"},"authors":[{"id":"3954cddf6608132f","avatar_url":"https://avatars.observableusercontent.com/avatar/02cfd655debb6fc593dfe4bcacd9520f73e6b33cedef382248d08ecc40822a1b","name":"Volodymyr Agafonkin","login":"mourner","bio":"Engineer at Mapbox, creator of Leaflet, algorithms geek, open source enthusiast, musician, father of twin girls, Ukrainian. 🇺🇦","home_url":"https://agafonkin.com","tier":"pro","approved":true,"description":""}],"collections":[{"id":"6aaf02cd1aea775c","type":"public","slug":"algorithms","title":"Algorithms & Math","description":"","update_time":"2020-04-30T13:55:57.350Z","pinned":false,"ordered":false,"custom_thumbnail":"bdf2ae376f4e8212d411ca823779379f6e75102cc08064fd2fb7dfa3a6c4fbed","default_thumbnail":"daad58cc9a35f5de2fdd9afc5ccf2f77d029c341caea4a104758227dfff14623","thumbnail":"bdf2ae376f4e8212d411ca823779379f6e75102cc08064fd2fb7dfa3a6c4fbed","listing_count":15,"parent_collection_count":0,"owner":{"id":"78f29d059906819a","avatar_url":"https://avatars.observableusercontent.com/avatar/02cfd655debb6fc593dfe4bcacd9520f73e6b33cedef382248d08ecc40822a1b","login":"mourner","name":"Volodymyr Agafonkin","bio":"Engineer at Mapbox, creator of Leaflet, algorithms geek, open source enthusiast, musician, father of twin girls, Ukrainian. 🇺🇦","home_url":"https://agafonkin.com","type":"team","tier":"starter_2024"}},{"id":"05c3a5b5466d0c69","type":"public","slug":"explorables","title":"Explorables","description":"","update_time":"2020-04-30T13:54:27.638Z","pinned":false,"ordered":false,"custom_thumbnail":"70c1f0ea770bdcb2b830946a62086dc6d05ce23baeb4e6f56b9da257ca7c081c","default_thumbnail":"0adf51ca7fec6a517f10fd007cf6aac62325e114a8e7da2594f09d66da29a7c0","thumbnail":"70c1f0ea770bdcb2b830946a62086dc6d05ce23baeb4e6f56b9da257ca7c081c","listing_count":14,"parent_collection_count":0,"owner":{"id":"78f29d059906819a","avatar_url":"https://avatars.observableusercontent.com/avatar/02cfd655debb6fc593dfe4bcacd9520f73e6b33cedef382248d08ecc40822a1b","login":"mourner","name":"Volodymyr Agafonkin","bio":"Engineer at Mapbox, creator of Leaflet, algorithms geek, open source enthusiast, musician, father of twin girls, Ukrainian. 🇺🇦","home_url":"https://agafonkin.com","type":"team","tier":"starter_2024"}},{"id":"7f9c38896ce07772","type":"public","slug":"personal","title":"Personal","description":"","update_time":"2020-04-30T13:39:18.080Z","pinned":false,"ordered":false,"custom_thumbnail":null,"default_thumbnail":"0adf51ca7fec6a517f10fd007cf6aac62325e114a8e7da2594f09d66da29a7c0","thumbnail":"0adf51ca7fec6a517f10fd007cf6aac62325e114a8e7da2594f09d66da29a7c0","listing_count":8,"parent_collection_count":0,"owner":{"id":"78f29d059906819a","avatar_url":"https://avatars.observableusercontent.com/avatar/02cfd655debb6fc593dfe4bcacd9520f73e6b33cedef382248d08ecc40822a1b","login":"mourner","name":"Volodymyr Agafonkin","bio":"Engineer at Mapbox, creator of Leaflet, algorithms geek, open source enthusiast, musician, father of twin girls, Ukrainian. 🇺🇦","home_url":"https://agafonkin.com","type":"team","tier":"starter_2024"}}],"files":[],"comments":[{"id":"4f5f662571a7d322","content":"I made a notebook exploring turning circles if anyone is interested: https://observablehq.com/@gampleman/turning-circle","node_id":544,"create_time":"2021-12-14T10:56:47.415Z","update_time":null,"resolved":false,"user":{"id":"34d15c3b1cc754ed","avatar_url":"https://avatars.observableusercontent.com/avatar/6ee048ed02abfd79e76f2f9dd7c7e8658c47b116d846f2f9ca8814ecdde18ffc","login":"gampleman","name":"Jakub Hampl","bio":"","home_url":"https://gampleman.eu","tier":"public"}}],"commenting_lock":null,"suggestion_from":null,"suggestions_to":[],"version":637,"title":"Kinematics of Reverse Angle Parking","license":null,"copyright":"","nodes":[{"id":0,"value":"md`# Kinematics of Reverse Angle Parking\n\nMy wife and me are driving beginners, and we still have a lot to learn — especially about parking. A few days ago, we got a new parking spot where we live, and the space turned out to be quite challenging: a very narrow one-way road with _bollards_ on the right (so even a slight mistake can lead to a huge dent in the car) and narrow [angled slots for reverse parking](https://en.wikipedia.org/wiki/Back-in_angle_parking) on the left, with the slots on either side of ours usually taken by other cars.\n\nIs it really even possible to park our car in a simple reverse motion in these tight conditions? As a geek without much driving experience who's still confused about the car movement pattern when turning in reverse, I decided to find out the answer using the tools I love — _mathematics_ and _data visualization_.\n\n## Ackermann Steering Model\n\nThe model below is derived from the [Ackermann steering geometry](https://en.wikipedia.org/wiki/Ackermann_steering_geometry), a steering design invented in 1817 for horse carriages, but still applicable today for modelling low-speed maneuvers of modern cars. The actual formulas used are helpfully described in the following articles: \n\n- [Ackerman Steering](https://www.xarg.org/book/kinematics/ackerman-steering/) by Robert Eisele\n- [Forward Kinematics of Car-Like Mechanisms](http://correll.cs.colorado.edu/?p=1869) by Nikolaus Correll\n\nGiven the actual specs of our car, the dimensions of the parking lot (had to go out and get those with a tape measure), and the starting position of the car, the orange area shows _the space through which the car will travel_ during reverse motion while turning at a given angle, then _straightening_ the wheel when the car aligns with the parking slot angle.\n`","pinned":false,"mode":"js","data":null,"name":null},{"id":82,"value":"viewof steerAngle = fancyRange({\n  min: 0, \n  max: maxTurnAngle, \n  value: maxTurnAngle,\n  label: v => html`${tex`${v.toFixed(2)}\\%`} — wheel turn angle`\n})","pinned":false,"mode":"js","data":null,"name":null},{"id":337,"value":"viewof startAngle = fancyRange({\n  min: 0, \n  max: parkAngle, \n  value: 0,\n  label: v => html`${tex`${v.toFixed(2)}\\%`} — starting heading`\n})","pinned":false,"mode":"js","data":null,"name":null},{"id":437,"value":"viewof startX = fancyRange({\n  min: 3.6, \n  max: 9, \n  value: 6.4,\n  label: v => html`${tex`${v.toFixed(2)}m`} — starting ${tex`x`}`\n})","pinned":false,"mode":"js","data":null,"name":null},{"id":360,"value":"viewof startY = fancyRange({\n  min: 0.95, \n  max: 2.85, \n  value: 2.2,\n  label: v => html`${tex`${v.toFixed(2)}m`} — starting ${tex`y`}`\n})","pinned":false,"mode":"js","data":null,"name":null},{"id":6,"value":"{\n  const w = Math.min(850, width);\n  const height = w * 442 / 800;\n  const ctx = DOM.context2d(w, height);\n  const p = 4;\n  const r = (height - p) / (roadWidth + lotWidth);\n  const lotOffset = lotWidth / Math.tan(parkAngle * rad);\n  const areaWidth = w / r;\n  const areaHeight = roadWidth + lotWidth;\n  \n  function line(x, y, t, dx, dy, start = false) {\n    const sin = Math.sin(t), cos = Math.cos(t);\n    const xt = r * (x + cos * dx - sin * dy);\n    const yt = p + r * (y + sin * dx + cos * dy);\n\n    if (start) ctx.moveTo(xt, yt);\n    else ctx.lineTo(xt, yt);\n  }\n  \n  function drawCar(x, y, t) {\n    const x0 = overhangRear;\n    const x1 = -wheelBase - overhangFront;\n    const y0 = -carWidth / 2;\n    const y1 = carWidth / 2;\n    line(x, y, t, x0, y0, true);\n    line(x, y, t, x0, y1);\n    line(x, y, t, x1, y1);\n    line(x, y, t, x1, y0);\n    ctx.closePath();\n  }\n    \n  let x = startX;\n  let y = startY;\n  let t = startAngle * rad;\n  let speed = 0.04;\n\n  // orange car path fill\n  ctx.beginPath();\n  drawCar(x, y, t);\n  while (x < areaWidth + wheelBase && y < areaHeight) {\n    x += speed * Math.cos(t);\n    y += speed * Math.sin(t);\n    t = Math.min(parkAngle * rad, t + speed * turningRate * rad);\n    drawCar(x, y, t);\n  }\n  ctx.fillStyle = '#ffae19';\n  ctx.fill();\n  \n  // car outline\n  ctx.lineWidth = 2;\n  ctx.beginPath();\n  t = startAngle * rad;\n  drawCar(startX, startY, t);\n  line(startX, startY, t, 0, -trackRear / 2, true);\n  line(startX, startY, t, 0, trackRear / 2);\n  line(startX, startY, t, 0, 0, true);\n  line(startX, startY, t, -wheelBase, 0);\n  line(startX, startY, t, -wheelBase, -trackFront / 2, true);\n  line(startX, startY, t, -wheelBase, trackFront / 2);\n  ctx.stroke();\n    \n  // parking lot lines\n  ctx.lineWidth = 4;\n  ctx.beginPath();\n  ctx.moveTo(0, p - 2);\n  ctx.lineTo(w, p - 2);\n  \n  for (let i = 0; i < 5; i++) {\n    ctx.moveTo(i * parkWidth * r, p + roadWidth * r);\n    ctx.lineTo((i * parkWidth + lotOffset) * r, p + (roadWidth + lotWidth) * r);\n  }\n  ctx.stroke();\n\n  // road direction arrow\n  const my = p + (roadWidth / 2) * r;\n  ctx.beginPath();\n  ctx.strokeStyle = '#aaa';\n  ctx.moveTo(w - 0.5 * r, my);\n  ctx.lineTo(w - 2 * r, my);\n  ctx.moveTo(w - 1.5 * r, my - 0.4 * r);\n  ctx.lineTo(w - 2 * r, my);\n  ctx.lineTo(w - 1.5 * r, my + 0.4 * r);\n  ctx.stroke();\n  \n  // dashed parking lot line\n  ctx.lineWidth = 2;\n  ctx.strokeStyle = 'black';\n  ctx.beginPath();\n  ctx.setLineDash([5, 5]);\n  ctx.moveTo(0, p + roadWidth * r);\n  ctx.lineTo(width, p + roadWidth * r);\n  ctx.stroke();  \n\n  return ctx.canvas;\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":482,"value":"md`\nSo, is it possible to park quickly in these conditions without colliding with anything? According to the model, it's indeed possible, but certainly not easy. The fact not all my neighbors are good at parking doesn't help either — their cars will often protrude way past the dashed line.\n\nHowever, this model helped me learn a lot about how car moves during reverse parking! It also gives me hope that we'll eventually adapt to the situation and learn to park there without fear and dozens of back-and-forth moves. Math & viz for the win!\n\nAny comments and corrections are welcome! Reply and share [on Twitter](https://twitter.com/mourner/status/1210561019275333633), or join the discussion [on HackerNews](https://news.ycombinator.com/item?id=21891919) or [/r/math](https://www.reddit.com/r/math/comments/egcxf3/kinematics_of_reverse_angle_parking/).\n`","pinned":false,"mode":"js","data":null,"name":null},{"id":553,"value":"md`## Key Calculations\n\nHow much the car turns as it moves with the given steering angle will depend on its wheel base length:`","pinned":false,"mode":"js","data":null,"name":null},{"id":305,"value":"turningRate = Math.tan(steerAngle * rad) / wheelBase / rad // degrees per meter","pinned":true,"mode":"js","data":null,"name":null},{"id":544,"value":"md`The maximum turning angle of the wheels can be calculated from the [turning circle](https://en.wikipedia.org/wiki/Turning_radius) value (from the car specs) together with the wheel base and the car width:`","pinned":false,"mode":"js","data":null,"name":null},{"id":125,"value":"maxTurnAngle = Math.atan2(wheelBase, (turningCircle - carWidth) / 2) / rad","pinned":true,"mode":"js","data":null,"name":null},{"id":568,"value":"md`In a real car, the actual angles of the front wheels will be slightly different to minimize tire skid (in accordance with the Ackermann model), but for the purpose of modeling the movement, we can assume there's a single wheel in the middle of the front axis — the trajectory will be the same.\n\nThe rest of the car specs will only affect the geometry of the car around its movement path, which is anchored at its rotational center — the middle spot between the back wheels.`","pinned":false,"mode":"js","data":null,"name":null},{"id":53,"value":"md`## Car Specs ([Hyundai i30 2018](https://www.car.info/en-se/hyundai/i30/i30-2018-10893631/specs))`","pinned":false,"mode":"js","data":null,"name":null},{"id":57,"value":"carWidth = 1.795","pinned":false,"mode":"js","data":null,"name":null},{"id":60,"value":"overhangFront = 0.905","pinned":false,"mode":"js","data":null,"name":null},{"id":76,"value":"overhangRear = 0.785","pinned":false,"mode":"js","data":null,"name":null},{"id":70,"value":"wheelBase = 2.65","pinned":false,"mode":"js","data":null,"name":null},{"id":71,"value":"trackFront = 1.573","pinned":false,"mode":"js","data":null,"name":null},{"id":193,"value":"trackRear = 1.581","pinned":false,"mode":"js","data":null,"name":null},{"id":131,"value":"turningCircle = 10.6","pinned":false,"mode":"js","data":null,"name":null},{"id":48,"value":"md`## Parking Lot Specs`","pinned":false,"mode":"js","data":null,"name":null},{"id":17,"value":"roadWidth = 3.80","pinned":false,"mode":"js","data":null,"name":null},{"id":20,"value":"parkWidth = 2.80","pinned":false,"mode":"js","data":null,"name":null},{"id":27,"value":"lotWidth = 3.8","pinned":false,"mode":"js","data":null,"name":null},{"id":21,"value":"parkAngle = 55","pinned":false,"mode":"js","data":null,"name":null},{"id":307,"value":"md`## Misc`","pinned":false,"mode":"js","data":null,"name":null},{"id":310,"value":"rad = Math.PI / 180","pinned":false,"mode":"js","data":null,"name":null},{"id":322,"value":"import {fancyRange} from '@mourner/non-robust-arithmetic-as-art'","pinned":false,"mode":"js","data":null,"name":null}],"resolutions":[],"schedule":null,"last_view_time":null}