diff --git a/__snapshots__/chromium_layers-gpx-layer--gpx-layer-default.png b/__snapshots__/chromium_layers-gpx-layer--gpx-layer-default.png
index e820170..6ee0697 100644
Binary files a/__snapshots__/chromium_layers-gpx-layer--gpx-layer-default.png and b/__snapshots__/chromium_layers-gpx-layer--gpx-layer-default.png differ
diff --git a/__snapshots__/chromium_layers-gpx-layer--gpx-layer-line-style.png b/__snapshots__/chromium_layers-gpx-layer--gpx-layer-line-style.png
index 470f41f..44fd23a 100644
Binary files a/__snapshots__/chromium_layers-gpx-layer--gpx-layer-line-style.png and b/__snapshots__/chromium_layers-gpx-layer--gpx-layer-line-style.png differ
diff --git a/__snapshots__/chromium_layers-gpx-layer--gpx-wms.png b/__snapshots__/chromium_layers-gpx-layer--gpx-wms.png
index ba292b0..a715686 100644
Binary files a/__snapshots__/chromium_layers-gpx-layer--gpx-wms.png and b/__snapshots__/chromium_layers-gpx-layer--gpx-wms.png differ
diff --git a/__snapshots__/webkit_layers-gpx-layer--gpx-layer-default.png b/__snapshots__/webkit_layers-gpx-layer--gpx-layer-default.png
index 1499a5d..811c139 100644
Binary files a/__snapshots__/webkit_layers-gpx-layer--gpx-layer-default.png and b/__snapshots__/webkit_layers-gpx-layer--gpx-layer-default.png differ
diff --git a/__snapshots__/webkit_layers-gpx-layer--gpx-layer-line-style.png b/__snapshots__/webkit_layers-gpx-layer--gpx-layer-line-style.png
index c0a467f..0bab6be 100644
Binary files a/__snapshots__/webkit_layers-gpx-layer--gpx-layer-line-style.png and b/__snapshots__/webkit_layers-gpx-layer--gpx-layer-line-style.png differ
diff --git a/__snapshots__/webkit_layers-gpx-layer--gpx-wms.png b/__snapshots__/webkit_layers-gpx-layer--gpx-wms.png
index 86de020..8164bf4 100644
Binary files a/__snapshots__/webkit_layers-gpx-layer--gpx-wms.png and b/__snapshots__/webkit_layers-gpx-layer--gpx-wms.png differ
diff --git a/src/index.ts b/src/index.ts
index 481f615..1f9f4a4 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -3,7 +3,7 @@ export {
ActivityMap,
ActivityMapProps,
} from "./components/activity-map";
-export { GpxLayer } from "./layers/gpx-layer";
+export { GpxLayer, GpxLayerProps } from "./layers/gpx-layer/gpx-layer";
export { GpxStreetMap } from "./components/gpx-street-map";
export { StreetLayer } from "./layers/street-layer";
export { FocusGpxStreetMap } from "./components/focus-gpx-street-map";
diff --git a/src/layers/gpx-layer.stories.tsx b/src/layers/gpx-layer.stories.tsx
deleted file mode 100644
index f0ae42c..0000000
--- a/src/layers/gpx-layer.stories.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-import { DeckGL } from "@deck.gl/react";
-import { StreetLayer } from "./street-layer";
-import { useMemo, useState } from "react";
-import * as React from "react";
-import { GpxLayer, GpxLayerProps } from "./gpx-layer";
-import { TerrainLayer } from "@deck.gl/geo-layers";
-import { LayersList, CompositeLayer } from "@deck.gl/core";
-import { Map } from "react-map-gl/maplibre";
-import type { StoryObj } from "@storybook/react";
-import {
- JR_ACTIVITY_FILE,
- JR_INITIAL_VIEW_STATE,
- JR_PITCHED_VIEW_STATE,
-} from "../constant.stories";
-
-export default {
- title: "Layers / GPX Layer",
- tags: ["autodocs"],
- parameters: {
- docs: {
- story: {
- height: "600px",
- },
- },
- },
-};
-
-const defaultLayerProps = {
- id: "gpx-layer",
- data: JR_ACTIVITY_FILE,
-};
-
-export function GPXLayerDefault() {
- const layer = new GpxLayer({ ...defaultLayerProps });
-
- return (
-
- );
-}
-
-export function GPXLayerLineStyle() {
- const layer = new GpxLayer({
- ...defaultLayerProps,
- lineWidthMinPixels: 5,
- getLineColor: [0, 0, 200],
- }) as CompositeLayer;
-
- return (
-
- );
-}
-
-export function GpxWms() {
- const gpxLayer = new GpxLayer({
- ...defaultLayerProps,
- }) as CompositeLayer;
-
- const layers = [gpxLayer, new StreetLayer()];
-
- return (
-
- );
-}
-
-export const GpxSatteliteTerrain: StoryObj = {
- render: () => {
- const layerProps: GpxLayerProps = {
- ...defaultLayerProps,
- getLineColor: [255, 255, 0],
- };
-
- const gpxLayer = new GpxLayer({ ...layerProps });
-
- const [key] = useState(import.meta.env.VITE_MAPTILER_API_KEY);
-
- const TERRAIN_IMAGE = `https://api.maptiler.com/tiles/terrain-rgb-v2/{z}/{x}/{y}.webp?key=${key}`;
- const SURFACE_IMAGE = `https://api.maptiler.com/tiles/satellite-v2/{z}/{x}/{y}.jpg?key=${key}`;
-
- const ELEVATION_DECODER = {
- rScaler: 6553.6,
- gScaler: 25.6,
- bScaler: 0.1,
- offset: -10000,
- };
-
- const terrainLayer = useMemo(
- () =>
- new TerrainLayer({
- id: "terrain",
- minZoom: 0,
- maxZoom: 12,
- elevationDecoder: ELEVATION_DECODER,
- elevationData: TERRAIN_IMAGE,
- texture: SURFACE_IMAGE,
- wireframe: false,
- color: [255, 255, 255],
- }),
- [ELEVATION_DECODER, TERRAIN_IMAGE, SURFACE_IMAGE],
- );
-
- const layers = [gpxLayer, terrainLayer];
-
- return (
-
- );
- },
- tags: ["no-visual-test"],
-};
-
-export function GpxMapTerrain() {
- const gpxLayer = new GpxLayer({ ...defaultLayerProps }) as CompositeLayer;
-
- const [API_KEY] = useState(import.meta.env.VITE_MAPTILER_API_KEY);
- const layers = [gpxLayer];
- const mapStyle = `https://api.maptiler.com/maps/streets-v2/style.json?key=${API_KEY}`;
-
- return (
-
-
-
- );
-}
-GpxMapTerrain.tags = ["no-visual-test"];
diff --git a/src/layers/gpx-layer.ts b/src/layers/gpx-layer.ts
deleted file mode 100644
index 41773ea..0000000
--- a/src/layers/gpx-layer.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import {
- GeoJsonLayer,
- PathLayer,
- type GeoJsonLayerProps,
-} from "@deck.gl/layers";
-import { Color } from "@deck.gl/core";
-import { GPXLoader, TCXLoader } from "@loaders.gl/kml";
-import * as _ from "lodash";
-
-export type GpxLayerProps = GeoJsonLayerProps;
-
-export class GpxLayer extends GeoJsonLayer {
- constructor(props: GpxLayerProps) {
- super({ ...props });
- }
-
- renderLayers() {
- const defaultLayers = super.renderLayers();
- const lineLayers = defaultLayers[1];
- if (!_.isArray(lineLayers) || lineLayers.length < 2) {
- return defaultLayers;
- }
- const pathLayer = lineLayers[1];
- if (!pathLayer || pathLayer.constructor.name !== "PathLayer") {
- return defaultLayers;
- }
- const outlineLayer = (pathLayer as PathLayer).clone({
- id: "outline",
- widthMinPixels: this.props.lineWidthMinPixels + 1,
- getColor: [0, 0, 0],
- });
- return [outlineLayer, ...defaultLayers];
- }
-}
-
-const defaultProps = {
- ...GeoJsonLayer.defaultProps,
- lineWidthMinPixels: 3,
- getLineColor: [255, 255, 0] as Color,
- loaders: [GPXLoader, TCXLoader],
-};
-
-GpxLayer.defaultProps = defaultProps;
-GpxLayer.layerName = "GpxLayer";
diff --git a/src/layers/gpx-layer/gpx-layer.stories.tsx b/src/layers/gpx-layer/gpx-layer.stories.tsx
new file mode 100644
index 0000000..1391808
--- /dev/null
+++ b/src/layers/gpx-layer/gpx-layer.stories.tsx
@@ -0,0 +1,133 @@
+import { DeckGL } from "@deck.gl/react";
+import { StreetLayer } from "../street-layer";
+import { useState } from "react";
+import * as React from "react";
+import { GpxLayer, GpxLayerProps } from "./gpx-layer";
+import { CompositeLayer } from "@deck.gl/core";
+import { Map } from "react-map-gl/maplibre";
+import type { StoryObj } from "@storybook/react";
+import {
+ JR_ACTIVITY_FILE,
+ JR_INITIAL_VIEW_STATE,
+ JR_PITCHED_VIEW_STATE,
+} from "../../constant.stories";
+import { HillLayer } from "../hill-layer";
+import { getRgba } from "../util.stories";
+
+export default {
+ title: "Layers / GPX Layer",
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ story: {
+ height: "600px",
+ },
+ },
+ },
+};
+
+const defaultLayerProps = {
+ id: "gpx-layer",
+ data: JR_ACTIVITY_FILE,
+};
+
+export const GPXLayerDefault: StoryObj = {
+ render: () => {
+ const layer = new GpxLayer({ ...defaultLayerProps });
+
+ return (
+
+ );
+ },
+};
+
+export const GPXLayerLineStyle: StoryObj<{ color: string } & GpxLayerProps> = {
+ args: {
+ color: "blue",
+ lineWidthMinPixels: 5,
+ },
+ render: ({ color, lineWidthMinPixels }) => {
+ const getLineColor = getRgba(color);
+
+ const layer = new GpxLayer({
+ ...defaultLayerProps,
+ lineWidthMinPixels,
+ getLineColor,
+ });
+
+ return (
+
+ );
+ },
+};
+
+export const GpxWms: StoryObj = {
+ render: () => {
+ const gpxLayer = new GpxLayer({
+ ...defaultLayerProps,
+ });
+
+ const layers = [gpxLayer, new StreetLayer()];
+
+ return (
+
+ );
+ },
+};
+
+export const GpxSatteliteTerrain: StoryObj = {
+ render: () => {
+ const layerProps: GpxLayerProps = {
+ ...defaultLayerProps,
+ getLineColor: [255, 255, 0],
+ };
+
+ const gpxLayer = new GpxLayer({ ...layerProps });
+
+ const layers = [gpxLayer, new HillLayer()];
+
+ return (
+
+ );
+ },
+ tags: ["no-visual-test"],
+};
+
+export function GpxMapTerrain() {
+ const gpxLayer = new GpxLayer({ ...defaultLayerProps }) as CompositeLayer;
+
+ const [API_KEY] = useState(import.meta.env.VITE_MAPTILER_API_KEY);
+ const layers = [gpxLayer];
+ const mapStyle = `https://api.maptiler.com/maps/streets-v2/style.json?key=${API_KEY}`;
+
+ return (
+
+
+
+ );
+}
+GpxMapTerrain.tags = ["no-visual-test"];
diff --git a/src/layers/gpx-layer/gpx-layer.ts b/src/layers/gpx-layer/gpx-layer.ts
new file mode 100644
index 0000000..2e7c3eb
--- /dev/null
+++ b/src/layers/gpx-layer/gpx-layer.ts
@@ -0,0 +1,24 @@
+import { GeoJsonLayer, type GeoJsonLayerProps } from "@deck.gl/layers";
+import { Color } from "@deck.gl/core";
+import { GPXLoader, TCXLoader } from "@loaders.gl/kml";
+import { ExtrudedPathLayer } from "../extruded-path-layer/extruded-path-layer";
+
+export type GpxLayerProps = GeoJsonLayerProps;
+
+export class GpxLayer extends GeoJsonLayer {}
+
+const defaultProps = {
+ ...GeoJsonLayer.defaultProps,
+ lineWidthMinPixels: 3,
+ getLineColor: [255, 255, 0] as Color,
+ loaders: [GPXLoader, TCXLoader],
+ _subLayerProps: {
+ linestrings: {
+ type: ExtrudedPathLayer,
+ getSideColor: [120, 120, 120, 255],
+ },
+ },
+};
+
+GpxLayer.defaultProps = defaultProps;
+GpxLayer.layerName = "GpxLayer";