Blog

  • Dymaxion

    SVG Downloads:
    https://narukawa-lab.jp/archives/dymaxion-map/

    D3-Projection Instruction
    https://observablehq.com/@fil/d3-projections

    https://observablehq.com/@d3/fullers-airocean

    D3JS Azimuthal

    https://d3js.org/d3-geo/azimuthal

    D3geo Projections

    https://d3js.org/d3-geo/projection

    D3 Projections Demo Page

    https://observablehq.com/@fil/d3-projections

    Phillippe Rivières Airocean implementation:
    https://observablehq.com/@fil/airocean-projection

    https://github.com/d3/d3-geo-polygon/blob/main/src/airocean.js

    Just look at it

    /*
     * Buckminster Fuller’s AirOcean arrangement of the icosahedron
     *
     * Implemented for D3.js by Jason Davies (2013),
     * Enrico Spinielli (2017) and Philippe Rivière (2017, 2018)
     *
     */
    import { atan, degrees } from "./math.js";
    import polyhedral from "./polyhedral/index.js";
    import { default as grayFullerRaw } from "./grayfuller.js";
    import {
      geoCentroid as centroid,
      geoContains as contains,
      geoGnomonic as gnomonic,
      geoProjection as projection
    } from "d3-geo";
    import { range } from "d3-array";
    
    function airoceanRaw(faceProjection) {
      const theta = atan(0.5) * degrees;
    
      // construction inspired by
      // https://en.wikipedia.org/wiki/Regular_icosahedron#Spherical_coordinates
      const vertices = [[0, 90], [0, -90]].concat(
        range(10).map((i) => [(i * 36 + 180) % 360 - 180, i & 1 ? theta : -theta])
      );
    
      // icosahedron
      const polyhedron = [
        [0, 3, 11],
        [0, 5, 3],
        [0, 7, 5],
        [0, 9, 7],
        [0, 11, 9], // North
        [2, 11, 3],
        [3, 4, 2],
        [4, 3, 5],
        [5, 6, 4],
        [6, 5, 7],
        [7, 8, 6],
        [8, 7, 9],
        [9, 10, 8],
        [10, 9, 11],
        [11, 2, 10], // Equator
        [1, 2, 4],
        [1, 4, 6],
        [1, 6, 8],
        [1, 8, 10],
        [1, 10, 2] // South
      ].map((face) => face.map((i) => vertices[i]));
    
      // add centroid
      polyhedron.forEach((face) => (face.centroid = centroid({ type: "MultiPoint", coordinates: face })));
    
      // split the relevant faces:
      // * face[15] in the centroid: this will become face[15], face[20] and face[21]
      // * face[14] in the middle of the side: this will become face[14] and face[22]
      (function() {
        let face, tmp, mid, centroid;
    
        // Split face[15] in 3 faces at centroid.
        face = polyhedron[15];
        centroid = face.centroid;
        tmp = face.slice();
        face[0] = centroid; // (new) face[15]
    
        face = [tmp[0], centroid, tmp[2]];
        face.centroid = centroid;
        polyhedron.push(face); // face[20]
    
        face = [tmp[0], tmp[1], centroid];
        face.centroid = centroid;
        polyhedron.push(face); // face[21]
    
        // Split face 14 at the edge.
        face = polyhedron[14];
        centroid = face.centroid;
        tmp = face.slice();
    
        // compute planar midpoint
        const proj = gnomonic()
          .scale(1)
          .translate([0, 0])
          .rotate([-centroid[0], -centroid[1]]);
        const a = proj(face[1]),
          b = proj(face[2]);
        mid = proj.invert([(a[0] + b[0]) / 2, (a[1] + b[1]) / 2]);
        face[1] = mid; // (new) face[14]
    
        // build the new half face
        face = [tmp[0], tmp[1], mid];
        face.centroid = centroid; // use original face[14] centroid
        polyhedron.push(face); // face[22]
    
        // cut face 19 to connect to 22
        face = polyhedron[19];
        centroid = face.centroid;
        tmp = face.slice();
        face[1] = mid;
    
        // build the new half face
        face = [mid, tmp[0], tmp[1]];
        face.centroid = centroid;
        polyhedron.push(face); // face[23]
      })();
    
      const airocean = function(faceProjection) {
        faceProjection =
          faceProjection ||
          // for half-triangles this is definitely not centroid({type: "MultiPoint", coordinates: face});
          ((face) => gnomonic()
              .scale(1)
              .translate([0, 0])
              .rotate([-face.centroid[0], -face.centroid[1]]));
    
        const faces = polyhedron.map((face, i) => {
          const polygon = face.slice();
          polygon.push(polygon[0]);
    
          return {
            face: face,
            site: face.centroid,
            id: i,
            contains: function(lambda, phi) {
              return contains({ type: "Polygon", coordinates: [polygon] }, [
                lambda * degrees,
                phi * degrees
              ]);
            },
            project: faceProjection(face)
          };
        });
    
        // Connect each face to a parent face.
        const parents = [
          // N
          -1, // 0
          0, // 1
          1, // 2
          11, // 3
          13, // 4
    
          // Eq
          6, // 5
          7, // 6
          1, // 7
          7, // 8
          8, // 9
    
          9, // 10
          10, // 11
          11, // 12
          12, // 13
          13, // 14
    
          // S
          6, // 15
          8, // 16
          10, // 17
          17, // 18
          21, // 19
          16, // 20
          15, // 21
          19, // 22
          19 // 23
        ];
    
        parents.forEach((d, i) => {
          const node = faces[d];
          node && (node.children || (node.children = [])).push(faces[i]);
        });
    
        function face(lambda, phi) {
          for (let i = 0; i < faces.length; ++i) {
            if (faces[i].contains(lambda, phi)) return faces[i];
          }
        }
    
        // Polyhedral projection
        const proj = polyhedral(
          faces[0], // the root face
          face // a function that returns a face given coords
        );
    
        proj.faces = faces;
        return proj;
      };
    
      return airocean(faceProjection);
    }
    
    export default function () {
      const p = airoceanRaw((face) => {
        const c = face.centroid;
    
        face.direction =
          Math.abs(c[1] - 52.62) < 1 || Math.abs(c[1] + 10.81) < 1 ? 0 : 60;
        return projection(grayFullerRaw())
          .scale(1)
          .translate([0, 0])
          .rotate([-c[0], -c[1], face.direction || 0]);
      });
    
      return p
        .rotate([-83.65929, 25.44458, -87.45184])
        .angle(-60)
        .scale(45.4631)
        .center([126, 0]);
    }
  • Bicycle Aerodynamics

    https://torstenfrank.wordpress.com/2021/12/07/aerodynamik-von-bikepacking-taschen-diese-taschen-machen-dich-schneller

    summary:

    Overview & Method

    • Torsten Frank ran extensive real-world tests across summer–autumn, logging many comparison rides with careful protocols.
    • He measured aerodynamic performance (CdA and watts) for eight different bikepacking setups.

    Key Findings

    1. Handlebar bags

    • Handlebar roll: Bad aerodynamics – about +2.1 aeroPOINTs slower than no bag.
    • Aerobar bag: Surprisingly good – –1 aeroPOINT faster than no bag at all.

    2. Rear bags

    • Tailfin AeroPack (trunkbag): Makes you 0.5 aeroPOINTs faster than no bag – aerodynamically beneficial.
    • Apidura Expedition Saddle Pack (14 L): Neutral – no faster, no slower than no bag.
    • Panniers:
      • 2 × 5 L Mini-Panniers (Tailfin): About 0.5 aeroPOINTs slower.
      • 2 × SL22 Panniers (~20 L each): About 0.9 aeroPOINTs slower.
      • One SL22 pannier instead of two Minis: also 0.5 aeroPOINTs slower, but with much higher volume (22 L vs 10 L).

    3. Combinations

    • Tailfin AeroPack + Mini Panniers (2 × 5 L) or Tailfin AeroPack + one SL22 pannier perform about the same as no bags or the Apidura Saddle Pack – but give you far more volume.
    • In practice, you can carry up to ~36 L of gear (AeroPack + SL22 pannier) with no aero penalty compared to a minimally loaded bike.

    Why does this happen?

    • Shape & placement matter:
      • Across-the-wind bags like handlebar rolls disrupt airflow → big drag penalty.
      • Streamlined bags behind the rider (AeroPack, aerobar bag) smooth airflow → sometimes reduce drag.
    • Aerobar bag acts like an aero fairing – it channels the wind like on a TT bike.
    • Side panniers sit in the wake of the rider → cause turbulence, but less severe than expected.

    Practical Takeaways

    • Avoid handlebar rolls if you care about aerodynamics.
    • Aerobar bags are a surprising aero win.
    • Best rear options:
      • Tailfin AeroPack → actually makes you faster.
      • Apidura 14 L Saddle Pack → aero-neutral, simple solution.
      • For bigger loads: Tailfin + Mini or SL22 panniers → up to 36 L with no aero loss.

    👉 So the choice isn’t about drag anymore – it’s about comfort, handling, and volume needs.

  • Trollmap

    Collect them trolls. Denmark, France, Benelux, UK

    https://trollmap.com

  • Climbfinder

    70k peaks in europe. Derived from strava stats, i guess. 3 views free, IP locked. 30€ p.a.

    Focus is on quality of routes and comments. Very valuable details. Search by average grade, most popular etc.

    https://climbfinder.com/de

  • Openrouteservice

    e.g. Isochrone

    https://openrouteservice.org/

    7000 request free /day. Isochrone up to 120km

    https://maps.openrouteservice.org/#