Skip to content

Commit

Permalink
init CustomLayer component
Browse files Browse the repository at this point in the history
  • Loading branch information
ciscorn committed Nov 28, 2024
1 parent 804bedb commit f74eb6f
Show file tree
Hide file tree
Showing 11 changed files with 205 additions and 13 deletions.
3 changes: 2 additions & 1 deletion src/content/examples/Index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
'/examples/geolocate': 'Locate the User',
'/examples/image-loader': 'Load Images from URLs',
'/examples/custom-control': 'Custom Control',
'/examples/custom-protocol': 'Custom Protocols'
'/examples/custom-protocol': 'Custom Protocols',
'/examples/custom-layer': 'Custom Layer'
}
},
{
Expand Down
7 changes: 4 additions & 3 deletions src/content/examples/custom-control/content.svelte.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ description: Custom Control allows to easily create user defined controls.
---

<script lang="ts">
import CustomControl from "./CustomControl.svelte";
import Demo from "./CustomControl.svelte";
import demoRaw from "./CustomControl.svelte?raw";
import CodeBlock from "../../CodeBlock.svelte";
let { shiki } = $props();
</script>

<CustomControl />
<Demo />

<CodeBlock content={demoRaw} />
<CodeBlock content={demoRaw} {shiki} />
89 changes: 89 additions & 0 deletions src/content/examples/custom-layer/CustomLayer.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<script lang="ts">
import { MapLibre, CustomLayer } from 'svelte-maplibre-gl';
import maplibregl from 'maplibre-gl';
class CustomLayerImpl implements Omit<maplibregl.CustomLayerInterface, 'id' | 'type'> {
program: WebGLProgram | null = null;
aPos: number = 0;
buffer: WebGLBuffer | null = null;
onAdd(_map: maplibregl.Map, gl: WebGL2RenderingContext) {
//create GLSL source for vertex shader
const vertexSource = `#version 300 es
uniform mat4 u_matrix;
in vec2 a_pos;
void main() {
gl_Position = u_matrix * vec4(a_pos, 0.0, 1.0);
}`;
// create GLSL source for fragment shader
const fragmentSource = `#version 300 es
out highp vec4 fragColor;
void main() {
fragColor = vec4(1.0, 0.0, 0.0, 0.5);
}`;
// create a vertex shader
const vertexShader = gl.createShader(gl.VERTEX_SHADER)!;
gl.shaderSource(vertexShader, vertexSource);
gl.compileShader(vertexShader);
// create a fragment shader
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)!;
gl.shaderSource(fragmentShader, fragmentSource);
gl.compileShader(fragmentShader);
// link the two shaders into a WebGL program
this.program = gl.createProgram()!;
gl.attachShader(this.program, vertexShader);
gl.attachShader(this.program, fragmentShader);
gl.linkProgram(this.program);
this.aPos = gl.getAttribLocation(this.program, 'a_pos');
// define vertices of the triangle to be rendered in the custom style layer
const helsinki = maplibregl.MercatorCoordinate.fromLngLat({ lng: 25.004, lat: 60.239 });
const berlin = maplibregl.MercatorCoordinate.fromLngLat({ lng: 13.403, lat: 52.562 });
const kyiv = maplibregl.MercatorCoordinate.fromLngLat({ lng: 30.498, lat: 50.541 });
console.log(helsinki, berlin, kyiv);
// create and initialize a WebGLBuffer to store vertex and color data
this.buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([helsinki.x, helsinki.y, berlin.x, berlin.y, kyiv.x, kyiv.y]),
gl.STATIC_DRAW
);
}
// method fired on each animation frame
render(gl: WebGL2RenderingContext | WebGLRenderingContext, options: maplibregl.CustomRenderMethodInput) {
gl.useProgram(this.program);
gl.uniformMatrix4fv(
gl.getUniformLocation(this.program!, 'u_matrix'),
false,
options.defaultProjectionData.mainMatrix
);
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
gl.enableVertexAttribArray(this.aPos);
gl.vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, 0, 0);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 3);
}
}
const customLayerImpl = new CustomLayerImpl();
</script>

<MapLibre
class="h-[60vh] min-h-[300px]"
style="https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json"
zoom={3}
center={[20, 58]}
antialias
>
<CustomLayer implementation={customLayerImpl} />
</MapLibre>
15 changes: 15 additions & 0 deletions src/content/examples/custom-layer/content.svelte.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
title: Custom style layer
description: Use a custom style layer to render custom WebGL content.
---

<script lang="ts">
import Demo from "./CustomLayer.svelte";
import demoRaw from "./CustomLayer.svelte?raw";
import CodeBlock from "../../CodeBlock.svelte";
let { shiki } = $props();
</script>

<Demo />

<CodeBlock content={demoRaw} shiki={shiki} />
7 changes: 4 additions & 3 deletions src/content/examples/custom-protocol/content.svelte.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ description: How to add custom protocols.
---

<script lang="ts">
import CustomProtocol from "./CustomProtocol.svelte";
import Demo from "./CustomProtocol.svelte";
import demoRaw from "./CustomProtocol.svelte?raw";
import CodeBlock from "../../CodeBlock.svelte";
let { shiki } = $props();
</script>

<CustomProtocol />
<Demo />

<CodeBlock content={demoRaw} />
<CodeBlock content={demoRaw} {shiki} />
1 change: 1 addition & 0 deletions src/lib/maplibre/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export { default as HeatmapLayer } from './layers/HeatmapLayer.svelte';
export { default as RasterLayer } from './layers/RasterLayer.svelte';
export { default as HillshadeLayer } from './layers/HillshadeLayer.svelte';
export { default as BackgroundLayer } from './layers/BackgroundLayer.svelte';
export { default as CustomLayer } from './layers/CustomLayer.svelte';

// markers
export { default as Marker } from './markers/Marker.svelte';
Expand Down
84 changes: 84 additions & 0 deletions src/lib/maplibre/layers/CustomLayer.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<script lang="ts">
// https://maplibre.org/maplibre-gl-js/docs/API/interfaces/CustomLayerInterface/
import { onDestroy, type Snippet } from 'svelte';
import maplibregl from 'maplibre-gl';
import { getMapContext } from '../contexts.svelte.js';
import { generateLayerID, resetLayerEventListener } from '../utils.js';
import type { MapLayerEventProps } from './common.js';
interface Props extends MapLayerEventProps {
id?: string;
beforeId?: string;
implementation: Omit<maplibregl.CustomLayerInterface, 'id' | 'type'>;
children?: Snippet;
}
let {
id: _id,
beforeId,
implementation,
children,
// Events
// https://maplibre.org/maplibre-gl-js/docs/API/type-aliases/MapLayerEventType/
onclick,
ondblclick,
onmousedown,
onmouseup,
onmousemove,
onmouseenter,
onmouseleave,
onmouseover,
onmouseout,
oncontextmenu,
ontouchstart,
ontouchend,
ontouchcancel
}: Props = $props();
const mapCtx = getMapContext();
if (!mapCtx.map) throw new Error('Map instance is not initialized.');
const id = _id ?? generateLayerID();
(implementation as maplibregl.CustomLayerInterface).id ??= id;
(implementation as maplibregl.CustomLayerInterface).type = 'custom';
let firstRun = true;
mapCtx.waitForStyleLoaded(() => {
mapCtx.addLayer(implementation as maplibregl.CustomLayerInterface, beforeId);
});
$effect(() => resetLayerEventListener(mapCtx.map, 'click', id, onclick));
$effect(() => resetLayerEventListener(mapCtx.map, 'dblclick', id, ondblclick));
$effect(() => resetLayerEventListener(mapCtx.map, 'mousedown', id, onmousedown));
$effect(() => resetLayerEventListener(mapCtx.map, 'mouseup', id, onmouseup));
$effect(() => resetLayerEventListener(mapCtx.map, 'mousemove', id, onmousemove));
$effect(() => resetLayerEventListener(mapCtx.map, 'mouseenter', id, onmouseenter));
$effect(() => resetLayerEventListener(mapCtx.map, 'mouseleave', id, onmouseleave));
$effect(() => resetLayerEventListener(mapCtx.map, 'mouseover', id, onmouseover));
$effect(() => resetLayerEventListener(mapCtx.map, 'mouseout', id, onmouseout));
$effect(() => resetLayerEventListener(mapCtx.map, 'contextmenu', id, oncontextmenu));
$effect(() => resetLayerEventListener(mapCtx.map, 'touchstart', id, ontouchstart));
$effect(() => resetLayerEventListener(mapCtx.map, 'touchend', id, ontouchend));
$effect(() => resetLayerEventListener(mapCtx.map, 'touchcancel', id, ontouchcancel));
$effect(() => {
if (beforeId && !firstRun) {
mapCtx.waitForStyleLoaded((map) => {
map.moveLayer(id, beforeId);
});
}
});
$effect(() => {
firstRun = false;
});
onDestroy(() => {
mapCtx.removeLayer(id);
});
</script>

{@render children?.()}
4 changes: 2 additions & 2 deletions src/routes/components/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
let { children } = $props();
</script>

<div class="mx-auto grid grid-cols-[220px_minmax(0,1fr)] gap-10 px-8 2xl:container">
<div class="mx-auto grid grid-cols-[200px_minmax(0,1fr)] gap-10 px-8 2xl:container">
<aside class="sticky bottom-0 top-16 h-[calc(100vh-4rem)] overflow-scroll py-8">
<Index />
</aside>
<main class="grid-cols-[1fr_220px]">
<main class="grid-cols-[1fr_200px]">
{@render children()}
</main>
</div>
2 changes: 1 addition & 1 deletion src/routes/components/[slug]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
const { data } = $props();
</script>

<div class="grid grid-cols-[1fr_220px] gap-x-10">
<div class="grid grid-cols-[1fr_200px] gap-x-10">
<div class="min-h-[calc(100vh-4rem)] w-full min-w-0 py-8">
<h1 class="mb-2 text-3xl font-bold">{data.meta.title}</h1>

Expand Down
4 changes: 2 additions & 2 deletions src/routes/examples/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
let { children } = $props();
</script>

<div class="mx-auto grid grid-cols-[220px_minmax(0,1fr)] gap-10 px-8 2xl:container">
<div class="mx-auto grid grid-cols-[200px_minmax(0,1fr)] gap-10 px-8 2xl:container">
<aside class="sticky bottom-0 top-16 h-[calc(100vh-4rem)] overflow-scroll py-8">
<Index />
</aside>
<main class="grid-cols-[1fr_220px]">
<main class="grid-cols-[1fr_200px]">
{@render children()}
</main>
</div>
2 changes: 1 addition & 1 deletion src/routes/examples/[slug]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
const { data } = $props();
</script>

<div class="grid grid-cols-[1fr_220px] gap-x-10">
<div class="grid grid-cols-[1fr_200px] gap-x-10">
<div class="min-h-[calc(100vh-4rem)] w-full min-w-0 py-8">
<h1 class="mb-2 text-3xl font-bold">{data.meta.title}</h1>

Expand Down

0 comments on commit f74eb6f

Please sign in to comment.