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.
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));
}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);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()))
);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
);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);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) });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);
}