Skip to content

Commit

Permalink
Merge branch 'main' into dependabot/npm_and_yarn/semantic-release-24.2.1
Browse files Browse the repository at this point in the history
  • Loading branch information
hkfb authored Feb 2, 2025
2 parents 76ee2aa + 5244a9e commit c3cc4d4
Show file tree
Hide file tree
Showing 35 changed files with 310 additions and 9 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ React/Typescript library for interactive 3D visualization of GPX and TCX activit

## Features
* [Deck.gl](https://deck.gl/) [layers](./src/layers/README.md) for visualization of GPX and TCX activities.
* [Deck.gl](https://deck.gl/) [layer extensions](./src/extensions/README.md) for path type layers
* Map components
* Activity Map - renders a GPX activity
* Focus Activity Map - renders a GPX trace and automatically centers the camera around the bounds of the displayed trace
Expand Down
Binary file modified __snapshots__/chromium_components-activity-maptiler--default.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __snapshots__/chromium_layers-activity-layer--picking.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __snapshots__/chromium_layers-gpx-layer--gpx-layer-default.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __snapshots__/chromium_layers-gpx-layer--gpx-wms.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __snapshots__/webkit_components-activity-maptiler--default.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __snapshots__/webkit_layers-activity-layer--picking.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __snapshots__/webkit_layers-extruded-path-layer--profile-width.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __snapshots__/webkit_layers-extruded-path-layer--side-color.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __snapshots__/webkit_layers-gpx-layer--gpx-layer-default.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __snapshots__/webkit_layers-gpx-layer--gpx-layer-line-style.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __snapshots__/webkit_layers-gpx-layer--gpx-wms.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions src/extensions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Deck.gl layer extensions

[Deck.gl](https://deck.gl/) layers extensions for visualization of GPX and TCX activities.

## Path Texture Extension
Colors a path using a texture.

## Path Distance Picking Extension
Allows picking the distance along a Path layer.
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { ExtrudedPathLayer } from "../../layers/extruded-path-layer/extruded-path-layer";
import { DeckGL } from "@deck.gl/react";
import * as React from "react";
import type { StoryObj } from "@storybook/react";
import { PathDistancePickingExtension } from "./path-distance-picking-extension";
import * as _ from "lodash";
import { MapViewState, type PickingInfo } from "@deck.gl/core";
import { StreetLayer } from "../../layers/street-layer";
import { SYNTHETIC_DATA } from "../../constant.stories";
import { userEvent, fireEvent } from "@storybook/test";

export default {
title: "Extensions / Path Distance Picking Extension",
tags: ["autodocs"],
};

const DEFAULT_PROPS = {
id: "extruded-path-layer",
getWidth: 3000,
extensions: [new PathDistancePickingExtension()],
pickable: true,
data: SYNTHETIC_DATA,
};

export const PathDistancePicking: StoryObj = {
render: () => {
const layer = new ExtrudedPathLayer({ ...DEFAULT_PROPS });

const initialViewState: MapViewState = {
zoom: 8,
latitude: 62.1,
longitude: 8.7,
pitch: 60,
};

const getTooltip = React.useCallback(
({ coordinate, picked, index }: PickingInfo) => {
if (!coordinate || _.isEmpty(coordinate) || !picked) {
return null;
}

const latTrunc = Math.floor(coordinate[1]);
const lonTrunc = Math.floor(coordinate[0]);

return {
html: `lat: ${latTrunc} lon: ${lonTrunc} distance: ${index}`,
};
},
[],
);

const base = new StreetLayer();

return (
<DeckGL
layers={[base, layer]}
initialViewState={initialViewState}
controller
getTooltip={getTooltip}
></DeckGL>
);
},
parameters: {
docs: {
description: {
story: "Pick the distance along the path.",
},
},
},
play: async () => {
const delay = 300;
const canvas = document.querySelector("canvas");

if (!canvas) {
return;
}

await userEvent.click(canvas, { delay });
await userEvent.hover(canvas, { delay });
await fireEvent.mouseMove(canvas, {
clientX: canvas.width / 2,
clientY: canvas.height / 2,
delay,
});
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"use strict";

import { Layer, LayerExtension, LayerContext, picking } from "@deck.gl/core";
import { vec3 } from "@math.gl/core";

/**
* A deck.gl layer extension for picking the distance along a path.
*/
export class PathDistancePickingExtension extends LayerExtension {
static extensionName = "PathDistancePickingExtension";
static defaultProps = {};

/**
* Called once when the layer is initialized. We add the `customPickingColors`
*/
override initializeState(
this: Layer,
context: LayerContext,
extension: this,
) {
const attributeManager = this.getAttributeManager();
if (!attributeManager) return;

attributeManager.addInstanced({
instanceDistAlongPath: {
size: 1,
accessor: "getPath",
transform: (path) =>
extension._getPerVertexDistances(path, this),
},
});
}

private _getPerVertexDistances(path: number[][], layer: Layer): number[] {
if (!Array.isArray(path) || path.length < 2) {
// Degenerate path -> single vertex, distance = 0
return [0, 0, 0];
}

// Project each [longitude, latitude] or [x, y]
const projectedPositions: [number, number, number][] = [];
for (const pt of path) {
const [x, y] = layer.projectPosition(pt);
projectedPositions.push([x, y, 0]);
}

// Compute cumulative distances in projected space
const distances = [0];
let totalDist = 0;
for (let i = 1; i < projectedPositions.length; i++) {
totalDist += vec3.dist(
projectedPositions[i],
projectedPositions[i - 1],
);
distances.push(totalDist);
}

return distances;
}

override getShaders() {
return {
name: "path-distance-picking-extension",
dependencies: [picking],
inject: {
// Vertex shader: declare and set the picking color
"vs:#decl": `
in float instanceDistAlongPath;
`,
"vs:#main-end": `
vec3 segmentStart = project_position(instanceStartPositions, instanceStartPositions64Low);
vec3 segmentEnd = project_position(instanceEndPositions, instanceEndPositions64Low);
vec3 deltaCommon = segmentEnd - segmentStart;
float segmentUnits = length(deltaCommon.xy);
float distUnits = mix(instanceDistAlongPath, instanceDistAlongPath + segmentUnits, isEnd);
float distMeters = distUnits / project_uCommonUnitsPerMeter.z / project_size();
picking_vRGBcolor_Avalid.r = distMeters;
`,
"fs:#decl": `
// Encode a float distance (0..16777215) into an RGB color.
vec3 encodeDistanceToRGB(float distance) {
float distClamped = clamp(distance, 0.0, 16777215.0);
int distInt = int(floor(distClamped + 0.5));
int r = distInt & 0xFF; // low byte
int g = (distInt >> 8) & 0xFF; // mid byte
int b = (distInt >> 16) & 0xFF; // high byte
return vec3(float(r + 1), float(g), float(b)) / 255.;
}
`,
"fs:#main-end": `
if (bool(picking.isActive)) {
fragColor.rgb = encodeDistanceToRGB(picking_vRGBcolor_Avalid.r);
}
`,
},
};
}
}
2 changes: 1 addition & 1 deletion src/layers/activity-layer/activity-layer.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ export const Picking: StoryObj = {
return null;
}
return {
html: `<p>lat: ${coordinate[1]}, lon: ${coordinate[0]} </p>`,
html: `lat: ${coordinate[1]}, lon: ${coordinate[0]}`,
};
}, []);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ uniform float miterLimit;
in vec4 vColor;
in vec2 vCornerOffset;
in float vMiterLength;
in vec3 vNormal;
in vec3 cameraPosition;
/*
* vPathPosition represents the relative coordinates of the current fragment on the path segment.
* vPathPosition.x - position along the width of the path, between [-1, 1]. 0 is the center line.
Expand All @@ -37,6 +40,7 @@ in float vMiterLength;
in vec2 vPathPosition;
in float vPathLength;
in float vJointType;
in vec3 vCommonPosition;
out vec4 fragColor;
Expand All @@ -53,7 +57,12 @@ void main(void) {
discard;
}
}
fragColor = vColor;
vec3 N = normalize(vNormal);
vec3 lightColor = lighting_getLightColor(vColor.rgb, cameraPosition, vCommonPosition, N);
fragColor = vec4(lightColor, vColor.a);
DECKGL_FILTER_COLOR(fragColor, geometry);
}
Expand Down
25 changes: 25 additions & 0 deletions src/layers/extruded-path-layer/extruded-path-layer-vertex.glsl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ out float vMiterLength;
out vec2 vPathPosition;
out float vPathLength;
out float vJointType;
out vec3 vNormal;
out vec3 vCommonPosition;
out vec3 cameraPosition;
const float EPSILON = 0.001;
const vec3 ZERO_OFFSET = vec3(0.0);
Expand Down Expand Up @@ -209,6 +212,8 @@ void main() {
currPosition = project_position(currPosition, currPosition64Low);
nextPosition = project_position(nextPosition, nextPosition64Low);
vCommonPosition = currPosition;
width = vec3(project_pixel_size(widthPixels), 0.0);
DECKGL_FILTER_SIZE(width, geometry);
Expand All @@ -223,5 +228,25 @@ void main() {
DECKGL_FILTER_GL_POSITION(gl_Position, geometry);
DECKGL_FILTER_COLOR(vColor, geometry);
// Compute normals
vec3 segment = nextPosition - currPosition;
float lenXY = length(segment.xy);
if (lenXY < 1e-6) {
lenXY = 1.0;
}
vec2 dir = segment.xy / lenXY;
// 2) Compute outward normal in XY plane (walls are vertical => no tilt in Z)
// This normal points "out" horizontally. If you want inside vs. outside, flip sign.
vec2 normalXY = vec2(-dir.y, dir.x);
normalXY = normalize(normalXY);
// 3) Approximate the normal
// For purely vertical walls, normal in world space is (nx, ny, 0).
// We'll store it as vNormal for the fragment shader.
vNormal = vec3(normalXY, 0.);
cameraPosition = project_uCameraPosition;
}
`;
54 changes: 53 additions & 1 deletion src/layers/extruded-path-layer/extruded-path-layer.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import { DeckGL } from "@deck.gl/react";
import * as React from "react";
import type { StoryObj } from "@storybook/react";
import { StreetLayer } from "../street-layer";
import { SYNTHETIC_VIEW_STATE, SYNTHETIC_DATA } from "../../constant.stories";
import {
SYNTHETIC_VIEW_STATE,
SYNTHETIC_DATA,
SYNTHETIC_PATH,
} from "../../constant.stories";
import { Matrix4 } from "@math.gl/core";
import { getRgba } from "../../util.stories";

Expand Down Expand Up @@ -244,3 +248,51 @@ export const ProfileWidth: StoryObj<{ width: number }> = {
},
},
};

export const SegmentColor: StoryObj = {
render: () => {
const path = new Float32Array(SYNTHETIC_PATH.flat());

const colors = [
[255, 0, 0],
[0, 255, 0],
[0, 0, 255],
[255, 255, 0],
[0, 255, 255],
[255, 0, 255],
];

const colorValues = new Uint8Array(colors.flat());

const data = {
length: 1,
startIndices: new Uint16Array([0]),
attributes: {
getPath: { value: path, size: 3 },
getColor: { value: colorValues, size: 3 },
},
};

const props = {
...DEFAULT_PROPS,
data,
};

const profile = new ExtrudedPathLayer({ ...props });

return (
<DeckGL
layers={[profile]}
initialViewState={SYNTHETIC_VIEW_STATE}
controller
></DeckGL>
);
},
parameters: {
docs: {
description: {
story: "Profile segment coloring.",
},
},
},
};
Loading

0 comments on commit c3cc4d4

Please sign in to comment.