or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

clipping.mdgeographic-calculations.mdindex.mdpath.mdprojections.mdshapes.mdtransformations.md
tile.json

transformations.mddocs/

Transformations

Low-level transformation functions for rotation, streaming, and coordinate system manipulation. These functions provide the foundation for d3-geo's geometry processing pipeline and enable custom transformations of geographic data.

Capabilities

Rotation

Creates rotation functions for rotating spherical coordinates around the sphere.

/**
 * Creates a rotation function for the specified angles
 * @param angles - Rotation angles as [λ, φ, γ] in degrees
 * @returns Rotation function with forward and inverse transforms
 */
function geoRotation(angles: [number, number, number?]): Rotation;

interface Rotation {
  /**
   * Rotates the specified point
   * @param point - Point as [longitude, latitude] in degrees
   * @returns Rotated point as [longitude, latitude]
   */
  (point: [number, number]): [number, number];
  
  /**
   * Inverse rotation of the specified point
   * @param point - Rotated point as [longitude, latitude] in degrees
   * @returns Original point as [longitude, latitude]
   */
  invert(point: [number, number]): [number, number];
}

Usage Examples:

import { geoRotation, geoPath, geoOrthographic } from "d3-geo";

// Create rotation around the Y-axis (longitude)
const longitudeRotation = geoRotation([90, 0, 0]); // Rotate 90° east

const originalPoint = [0, 0];     // Prime meridian, equator
const rotatedPoint = longitudeRotation(originalPoint); // [-90, 0]
const restored = longitudeRotation.invert(rotatedPoint); // [0, 0]

// Create rotation around X-axis (latitude)
const latitudeRotation = geoRotation([0, 45, 0]); // Rotate 45° north

// Composite rotation (longitude, latitude, and roll)
const compositeRotation = geoRotation([30, -15, 10]);

// Use rotation with projections
const projection = geoOrthographic()
  .rotate([90, -30, 0]); // Equivalent to geoRotation([90, -30, 0])

// Interactive globe rotation
function rotateGlobe(longitude, latitude, roll = 0) {
  const rotation = geoRotation([longitude, latitude, roll]);
  
  // Apply rotation to all features
  const rotatedFeatures = worldData.features.map(feature => ({
    ...feature,
    geometry: {
      ...feature.geometry,
      coordinates: transformCoordinates(feature.geometry.coordinates, rotation)
    }
  }));
  
  return rotatedFeatures;
}

function transformCoordinates(coords, rotation) {
  if (typeof coords[0] === 'number') {
    return rotation(coords);
  }
  return coords.map(coord => transformCoordinates(coord, rotation));
}

Streaming Interface

Streams geometry through a transform pipeline for memory-efficient processing.

/**
 * Streams the specified GeoJSON object to the given stream
 * @param object - GeoJSON feature or geometry to stream
 * @param stream - Transform stream to receive the geometry
 */
function geoStream(object: GeoJSON.Feature | GeoJSON.Geometry, stream: Transform): void;

interface Transform {
  /**
   * Called for each point in the geometry
   * @param x - X coordinate (longitude in degrees for spherical, projected x for planar)
   * @param y - Y coordinate (latitude in degrees for spherical, projected y for planar)
   */
  point?(x: number, y: number): void;
  
  /**
   * Called at the start of each line string
   */
  lineStart?(): void;
  
  /**
   * Called at the end of each line string
   */
  lineEnd?(): void;
  
  /**
   * Called at the start of each polygon
   */
  polygonStart?(): void;
  
  /**
   * Called at the end of each polygon
   */
  polygonEnd?(): void;
  
  /**
   * Called for the sphere (entire globe)
   */
  sphere?(): void;
}

Usage Examples:

import { geoStream } from "d3-geo";

// Create a logging stream to track geometry processing
const loggingStream = {
  point(x, y) {
    console.log(`Point: ${x}, ${y}`);
  },
  lineStart() {
    console.log("Line start");
  },
  lineEnd() {
    console.log("Line end");
  },
  polygonStart() {
    console.log("Polygon start");
  },
  polygonEnd() {
    console.log("Polygon end");
  }
};

// Stream a geometry through the logger
const lineString = {
  type: "LineString",
  coordinates: [[-74, 40], [-73, 41], [-72, 42]]
};

geoStream(lineString, loggingStream);
// Output:
// Line start
// Point: -74, 40
// Point: -73, 41
// Point: -72, 42
// Line end

// Create a point collection stream
const pointCollector = {
  points: [],
  point(x, y) {
    this.points.push([x, y]);
  }
};

geoStream(someGeometry, pointCollector);
console.log("Collected points:", pointCollector.points);

Transform Factory

Creates custom transform streams with specified methods.

/**
 * Creates a transform stream with the specified methods
 * @param methods - Transform methods object
 * @returns Transform function that creates stream instances
 */
function geoTransform(methods: TransformMethods): TransformFunction;

interface TransformMethods {
  point?(x: number, y: number): void;
  lineStart?(): void;
  lineEnd?(): void;
  polygonStart?(): void;
  polygonEnd?(): void;
  sphere?(): void;
}

interface TransformFunction {
  /**
   * Creates a transform stream for the specified output stream
   * @param stream - Output stream
   * @returns Transform stream
   */
  stream(stream: Transform): Transform;
}

Usage Examples:

import { geoTransform, geoStream, geoPath } from "d3-geo";

// Create a coordinate scaling transform
const scaleTransform = geoTransform({
  point(x, y) {
    this.stream.point(x * 2, y * 2); // Double all coordinates
  }
});

// Create a coordinate offset transform
const offsetTransform = geoTransform({
  point(x, y) {
    this.stream.point(x + 10, y + 5); // Offset all coordinates
  }
});

// Create a filtering transform that only passes certain points
const filterTransform = geoTransform({
  point(x, y) {
    if (x > -100 && x < 100) { // Only pass points in longitude range
      this.stream.point(x, y);
    }
  }
});

// Use transforms with path generation
const projection = geoMercator();
const path = geoPath(projection);

// Apply scaling transform to geometry before projection
const scaledPath = geoPath(
  scaleTransform.stream(projection.stream(path.context()))
);

Custom Transform Examples

import { geoTransform, geoPath, geoMercator } from "d3-geo";

// Create a jitter transform that adds random noise
const jitterTransform = geoTransform({
  point(x, y) {
    const jitterX = x + (Math.random() - 0.5) * 0.1;
    const jitterY = y + (Math.random() - 0.5) * 0.1;
    this.stream.point(jitterX, jitterY);
  }
});

// Create a quantization transform that rounds coordinates
const quantizeTransform = geoTransform({
  point(x, y) {
    const quantizedX = Math.round(x * 10) / 10;
    const quantizedY = Math.round(y * 10) / 10;
    this.stream.point(quantizedX, quantizedY);
  }
});

// Create a coordinate system conversion transform
const toRadiansTransform = geoTransform({
  point(x, y) {
    this.stream.point(x * Math.PI / 180, y * Math.PI / 180);
  }
});

// Chain multiple transforms
function chainTransforms(...transforms) {
  return {
    stream(stream) {
      return transforms.reduceRight((s, t) => t.stream(s), stream);
    }
  };
}

const combinedTransform = chainTransforms(
  jitterTransform,
  quantizeTransform,
  offsetTransform
);

Statistical Transform

import { geoTransform, geoStream } from "d3-geo";

// Create a statistics-gathering transform
function createStatsTransform() {
  let pointCount = 0;
  let xSum = 0;
  let ySum = 0;
  let xMin = Infinity;
  let xMax = -Infinity;
  let yMin = Infinity;
  let yMax = -Infinity;
  
  const statsTransform = geoTransform({
    point(x, y) {
      pointCount++;
      xSum += x;
      ySum += y;
      xMin = Math.min(xMin, x);
      xMax = Math.max(xMax, x);
      yMin = Math.min(yMin, y);
      yMax = Math.max(yMax, y);
      
      this.stream.point(x, y); // Pass through unchanged
    }
  });
  
  statsTransform.getStats = function() {
    return {
      count: pointCount,
      center: [xSum / pointCount, ySum / pointCount],
      bounds: [[xMin, yMin], [xMax, yMax]]
    };
  };
  
  return statsTransform;
}

// Use statistics transform
const statsTransform = createStatsTransform();
const outputStream = { point() {} }; // Dummy output

geoStream(someGeometry, statsTransform.stream(outputStream));
const stats = statsTransform.getStats();
console.log("Geometry statistics:", stats);

Projection Transform Chain

import { geoTransform, geoMercator, geoPath } from "d3-geo";

// Create a complete transformation pipeline
function createTransformPipeline(projection) {
  // Pre-projection transform: coordinate validation
  const validateTransform = geoTransform({
    point(x, y) {
      if (x >= -180 && x <= 180 && y >= -90 && y <= 90) {
        this.stream.point(x, y);
      }
    }
  });
  
  // Post-projection transform: coordinate clamping
  const clampTransform = geoTransform({
    point(x, y) {
      const clampedX = Math.max(-1000, Math.min(1000, x));
      const clampedY = Math.max(-1000, Math.min(1000, y));
      this.stream.point(clampedX, clampedY);
    }
  });
  
  return {
    stream(stream) {
      return validateTransform.stream(
        projection.stream(
          clampTransform.stream(stream)
        )
      );
    }
  };
}

// Use the transform pipeline
const projection = geoMercator();
const pipeline = createTransformPipeline(projection);
const path = geoPath({ stream: pipeline.stream.bind(pipeline) });

Animated Transforms

import { geoTransform, geoStream } from "d3-geo";

// Create an animated rotation transform
function createAnimatedRotation(duration = 5000) {
  let startTime = Date.now();
  
  return geoTransform({
    point(x, y) {
      const elapsed = Date.now() - startTime;
      const t = (elapsed % duration) / duration;
      const angle = t * 2 * Math.PI;
      
      // Rotate around origin
      const cos = Math.cos(angle);
      const sin = Math.sin(angle);
      const rotatedX = x * cos - y * sin;
      const rotatedY = x * sin + y * cos;
      
      this.stream.point(rotatedX, rotatedY);
    }
  });
}

// Create an animated scaling transform
function createPulsingScale(period = 2000, minScale = 0.5, maxScale = 1.5) {
  let startTime = Date.now();
  
  return geoTransform({
    point(x, y) {
      const elapsed = Date.now() - startTime;
      const t = (elapsed % period) / period;
      const scale = minScale + (maxScale - minScale) * (1 + Math.sin(t * 2 * Math.PI)) / 2;
      
      this.stream.point(x * scale, y * scale);
    }
  });
}

// Use animated transforms in render loop
function animatedRender() {
  const animatedTransform = createAnimatedRotation();
  const animatedPath = geoPath({
    stream: animatedTransform.stream.bind(animatedTransform)
  });
  
  svg.selectAll("path")
    .attr("d", animatedPath);
  
  requestAnimationFrame(animatedRender);
}