-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add utility for loading images from URLs
- Loading branch information
Showing
10 changed files
with
256 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
<script lang="ts"> | ||
import { MapLibre, ImageLoader, GeoJSONSource, SymbolLayer } from 'svelte-maplibre-gl'; | ||
import type { FeatureCollection } from 'geojson'; | ||
let data: FeatureCollection = { | ||
type: 'FeatureCollection', | ||
features: [ | ||
{ | ||
type: 'Feature', | ||
geometry: { type: 'Point', coordinates: [-48.47279, -1.44585] }, | ||
properties: { imageName: 'osgeo', year: 2024 } | ||
}, | ||
{ | ||
type: 'Feature', | ||
geometry: { type: 'Point', coordinates: [0, 0] }, | ||
properties: { imageName: 'cat', scale: 0.2 } | ||
}, | ||
{ | ||
type: 'Feature', | ||
geometry: { type: 'Point', coordinates: [40, -30] }, | ||
properties: { imageName: 'popup-debug', name: 'Line 1\nLine 2\nLine 3' } | ||
}, | ||
{ | ||
type: 'Feature', | ||
geometry: { type: 'Point', coordinates: [-40, -30] }, | ||
properties: { imageName: 'popup-debug', name: 'One longer line' } | ||
} | ||
] | ||
}; | ||
</script> | ||
|
||
<MapLibre | ||
class="h-[60vh] min-h-[300px]" | ||
style="https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json" | ||
zoom={1.5} | ||
center={{ lng: -10.0, lat: -20 }} | ||
> | ||
<GeoJSONSource {data}> | ||
<ImageLoader | ||
images={{ | ||
osgeo: 'https://maplibre.org/maplibre-gl-js/docs/assets/osgeo-logo.png', | ||
cat: 'https://upload.wikimedia.org/wikipedia/commons/7/7c/201408_cat.png', | ||
'popup-debug': [ | ||
'https://maplibre.org/maplibre-gl-js/docs/assets/popup_debug.png', | ||
{ | ||
// stretchable image | ||
stretchX: [ | ||
[25, 55], | ||
[85, 115] | ||
], | ||
stretchY: [[25, 100]], | ||
content: [25, 25, 115, 100], | ||
pixelRatio: 2 | ||
} | ||
] | ||
}} | ||
> | ||
<!-- Children components will be added after all images have been loaded --> | ||
<SymbolLayer | ||
layout={{ | ||
'text-field': ['get', 'name'], | ||
'icon-image': ['get', 'imageName'], | ||
'icon-size': ['number', ['get', 'scale'], 1], | ||
'icon-text-fit': 'both', | ||
'icon-overlap': 'always', | ||
'text-overlap': 'always' | ||
}} | ||
/> | ||
</ImageLoader> | ||
</GeoJSONSource> | ||
</MapLibre> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
--- | ||
title: Loading Images | ||
description: Utility for loading images from URLs | ||
--- | ||
|
||
<script lang="ts"> | ||
import Demo from "./Images.svelte"; | ||
import demoRaw from "./Images.svelte?raw"; | ||
import CodeBlock from "../../CodeBlock.svelte"; | ||
</script> | ||
|
||
<Demo /> | ||
|
||
<CodeBlock content={demoRaw} /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
<script lang="ts"> | ||
import { onDestroy, type Snippet } from 'svelte'; | ||
import { getMapContext } from '../contexts.svelte.js'; | ||
import maplibregl from 'maplibre-gl'; | ||
const mapCtx = getMapContext(); | ||
if (!mapCtx.map) throw new Error('Map instance is not initialized.'); | ||
let { | ||
images, | ||
loading = $bindable(), | ||
children | ||
}: { | ||
images: Record<string, string | [string, Partial<maplibregl.StyleImageMetadata>]>; | ||
loading?: boolean; | ||
children?: Snippet; | ||
} = $props(); | ||
let initialLoaded = $state(false); | ||
// map from loaded image id to url | ||
const loadedImages: Map<string, string> = new Map(); | ||
$effect(() => { | ||
// Remove images that are not in the new list or have a different url | ||
for (const [id, url] of loadedImages) { | ||
const src = images[id]; | ||
if (src) { | ||
const newUrl = Array.isArray(src) ? src[0] : src; | ||
if (url === newUrl) { | ||
continue; | ||
} else { | ||
loadedImages.delete(id); | ||
} | ||
} else { | ||
loadedImages.delete(id); | ||
mapCtx.map?.removeImage(id); | ||
} | ||
} | ||
// Load and add images that are not already loaded | ||
const tasks = []; | ||
for (const [id, src] of Object.entries(images)) { | ||
// if already loaded | ||
if (loadedImages.has(id)) { | ||
// Update image options if necessary | ||
const image = mapCtx.map?.getImage(id); | ||
if (image) { | ||
const options = Array.isArray(src) ? src[1] : {}; | ||
let changed = false; | ||
if (image.pixelRatio !== (options.pixelRatio ?? 1)) { | ||
image.pixelRatio = options.pixelRatio ?? 1; | ||
changed = true; | ||
} | ||
if (image.sdf !== (options.sdf ?? false)) { | ||
image.sdf = options.sdf ?? false; | ||
changed = true; | ||
} | ||
if (image.stretchX !== options.stretchX) { | ||
image.stretchX = options.stretchX; | ||
changed = true; | ||
} | ||
if (image.stretchY !== options.stretchY) { | ||
image.stretchY = options.stretchY; | ||
changed = true; | ||
} | ||
if (image.content !== options.content) { | ||
image.content = options.content; | ||
changed = true; | ||
} | ||
if (image.textFitHeight !== options.textFitHeight) { | ||
image.textFitHeight = options.textFitHeight; | ||
changed = true; | ||
} | ||
if (image.textFitWidth !== options.textFitWidth) { | ||
image.textFitWidth = options.textFitWidth; | ||
changed = true; | ||
} | ||
if (changed) { | ||
mapCtx.map?.style.updateImage(id, image); | ||
} | ||
} | ||
continue; | ||
} | ||
const [url, options] = Array.isArray(src) ? src : [src, undefined]; | ||
loadedImages.set(id, url); | ||
tasks.push( | ||
(async () => { | ||
const image = await mapCtx.map?.loadImage(url); | ||
if (mapCtx.map?.getImage(id)) { | ||
mapCtx.map?.removeImage(id); | ||
} | ||
if (image && loadedImages.has(id)) { | ||
mapCtx.map?.addImage(id, image?.data, options); | ||
} | ||
})() | ||
); | ||
} | ||
if (tasks.length > 0) { | ||
loading = true; | ||
Promise.allSettled([tasks]).then((results) => { | ||
const failures = results.filter((r) => r.status === 'rejected'); | ||
if (failures.length > 0) { | ||
console.error(`${failures.length} images failed to load:`, failures); | ||
} | ||
initialLoaded = true; | ||
loading = false; | ||
}); | ||
} else { | ||
initialLoaded = true; | ||
} | ||
}); | ||
onDestroy(() => { | ||
for (const id of loadedImages.keys()) { | ||
mapCtx.map?.removeImage(id); | ||
} | ||
}); | ||
</script> | ||
|
||
{#if initialLoaded} | ||
{@render children?.()} | ||
{/if} |