Giter Site home page Giter Site logo

dimfeld / svelte-maplibre Goto Github PK

View Code? Open in Web Editor NEW
252.0 8.0 30.0 1.51 MB

Svelte bindings for the MapLibre mapping library

Home Page: https://svelte-maplibre.vercel.app

License: MIT License

JavaScript 1.20% TypeScript 13.74% HTML 0.39% Svelte 84.65% CSS 0.03%
maplibre maplibre-gl-js mapping svelte

svelte-maplibre's Introduction

svelte-maplibre logo

Svelte wrapper for the maplibre mapping library.


This library is functional, but I'm still experimenting with extra features to make advanced functionality easier to use. It also needs proper documentation. In the meantime, the demo site includes code for all the examples, and is a good place to start.

Changelog

Installation

npm install svelte-maplibre

Usage

<script>
  import { MapLibre } from 'svelte-maplibre';
</script>

<MapLibre 
  center={[50,20]}
  zoom={7}
  class="map"
  standardControls
  style="https://basemaps.cartocdn.com/gl/positron-gl-style/style.json" />

<style>
  :global(.map) {
    height: 500px;
  }
</style>

Developing

You should create a .env file to configure the environment. See the .env.example file for the necessary settings.

Currently the only environment variable is a MapTiler API key, required for the 3D Buildings example. If you don't have one, you can set this to a blank value to use the other examples.

Credits

Logo created by Bruce Wayyn

svelte-maplibre's People

Contributors

brucewayyn avatar dabreegster avatar dimfeld avatar fluffydiscord avatar gka avatar jamesscottbrown avatar knd775 avatar mhrgoldberg avatar pierosavi avatar sidd27 avatar singingwolfboy avatar stephane avatar stuartlynn avatar torsteinringnes avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

svelte-maplibre's Issues

Add ContextMenu component

Render what's in the slot upon a contextmenu event, possibly using a Popup. Should work with markers and normal map clicks, and expose the coordinates, marker and/or features clicked in the slot.

Draggable markers should support bind:feature

When a marker is draggable, it should be able to update the feature using 2-way binding. This should figure out if it has a Location, a Point, or a Feature<Point>, and create an object of the same type that it was given.

Resize issue

Anyone know how to programmatically call the maplibre resize() method from svelte-maplibre?
I'm having an issue where maps don't stretch to fill their parent container when they are loaded.
Using vanilla Javascript I get get the map to expand by issuing map.resize() immediately after load.
Through various tests and googling it appears that the canvas is not picking up the parent container's size when it loads. Regardless, need to resize the map as the window is resized so it would be nice to have access to this method through the svelte component.
Any ideas?

Thanks!

Add a way of using maplibre.addprotocol

In order to display Cloud Optimized Geotiffs, a little bit of extra legwork is required with maplibre at the moment, as explained here.

In time, maybe maplibre will add this in the library, but this will most likely still require new dependencies (geotiff and fast png), so it could remain optional.

I will admit I am not tooo sure of what I am doing here. But I thought fixing this would involve making maplibregl available in the context, in order to be able to call addProtocol on it in a new COGLayer component (probably in a context="module" script tag to only add the protocol once to maplibregl).

Any opinions on this?

I am happy to try making a PR, but might need some guidance!

After baselayer switch GeoJSON source data cannot be updated

I have had a need for a while to switch baselayers, so much thanks to those who contributed to the recent updates enabling this.

I have found a bug which I have recreated in the demo site. When the baselayer is switched and the sources are re-added to the map the source component does not reset the local reference to the sourceObj so when data in the source layer is updated it does not show up on the map. You can see this behavior in the below video:

not-reactive.mov

We can fix this by having the GeoJSON component listen to the style.load event and update the sourceObj whenever it loads. I will create a PR momentarily to address this.

Flickering with hover popups on markers

Probable solution: have the popup use both the marker's hover state and its own info about if the mouse is over itself to determine if it should hide. Currently it only uses the former.

Support clustering

Maplibre includes support for this. Need to look at how it works and translate that support to a way that makes sense for Svelte

Support contextmenu event

Hello again !

If I am not mistaken, right click event (contextmenu) is not yet supported on layers.

Could you please add it ?

Thank you

Updating `Marker` `class` breaks it's position and scaling

I am experiencing weird behavior where updating markers class makes it jump to the left for some unknown reason and then when zooming or moving it's position is "scaling" instead of moving along the map. For simplicity sake I am updating brightness and making it black in this example video.

2023-09-04.17-50-36.online-video-cutter.com.mp4
<Marker class="relative group group-hover:z-10 hover:z-10 transition-colors {isSelected ? 'brightness-0' :''}"
                    lngLat={marker.position}
                    on:click={() => handleSelectMarker(marker)}
            >
</Marker>

Search map

Does anyone know how to implement search on the map if I type some city etc?

beforeId upgrades

  • Call moveLayer when beforeId changes
  • if beforeId is set and the layer doesn't exist yet, ignore it

Support Popups

  • On marker click
  • On GeoJSON feature click, with information about the specific feature when it's in one if a FeatureCollection
  • On general map click, with long/lat if it's not on something particular

Import errors with SvelteKit

Hey! Apologies if I'm just doing something obviously wrong.

I've initialised a new SvelteKit project, install the package with npm i svelte-maplibre

When I run my app with the following +page.svelte:

<script lang="ts">
	import {MapLibre} from "svelte-maplibre";
</script>
  
  <MapLibre
    style="https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
    class="relative w-full aspect-[9/16] max-h-[70vh] sm:max-h-full sm:aspect-video"
    standardControls
  />

I get:

21:51:54 [vite] Error when evaluating SSR module /src/routes/+page.svelte: failed to import "svelte-maplibre"
|- Error: Failed to resolve entry for package "svelte-maplibre". The package may have incorrect main/module/exports specified in its package.json: No known conditions for "." specifier in "svelte-maplibre" package
    at packageEntryFailure (file:///Users/ciaran/dev/archive28/node_modules/vite/dist/node/chunks/dep-75f53616.js:28687:11)
    at resolvePackageEntry (file:///Users/ciaran/dev/archive28/node_modules/vite/dist/node/chunks/dep-75f53616.js:28682:9)
    at tryNodeResolve (file:///Users/ciaran/dev/archive28/node_modules/vite/dist/node/chunks/dep-75f53616.js:28423:20)
    at nodeImport (file:///Users/ciaran/dev/archive28/node_modules/vite/dist/node/chunks/dep-75f53616.js:55953:26)
    at ssrImport (file:///Users/ciaran/dev/archive28/node_modules/vite/dist/node/chunks/dep-75f53616.js:55863:30)
    at eval (/Users/ciaran/dev/archive28/src/routes/+page.svelte:4:37)
    at async instantiateModule (file:///Users/ciaran/dev/archive28/node_modules/vite/dist/node/chunks/dep-75f53616.js:55925:9)

Internal server error: Failed to resolve entry for package "svelte-maplibre". The package may have incorrect main/module/exports specified in its package.json: No known conditions for "." specifier in "svelte-maplibre" package
      at packageEntryFailure (file:///Users/ciaran/dev/archive28/node_modules/vite/dist/node/chunks/dep-75f53616.js:28687:11)
      at resolvePackageEntry (file:///Users/ciaran/dev/archive28/node_modules/vite/dist/node/chunks/dep-75f53616.js:28682:9)
      at tryNodeResolve (file:///Users/ciaran/dev/archive28/node_modules/vite/dist/node/chunks/dep-75f53616.js:28423:20)
      at nodeImport (file:///Users/ciaran/dev/archive28/node_modules/vite/dist/node/chunks/dep-75f53616.js:55953:26)
      at ssrImport (file:///Users/ciaran/dev/archive28/node_modules/vite/dist/node/chunks/dep-75f53616.js:55863:30)
      at eval (/Users/ciaran/dev/archive28/src/routes/+page.svelte:4:37)
      at async instantiateModule (file:///Users/ciaran/dev/archive28/node_modules/vite/dist/node/chunks/dep-75f53616.js:55925:9)
Error: Failed to resolve entry for package "svelte-maplibre". The package may have incorrect main/module/exports specified in its package.json: No known conditions for "." specifier in "svelte-maplibre" package
    at packageEntryFailure (file:///Users/ciaran/dev/archive28/node_modules/vite/dist/node/chunks/dep-75f53616.js:28687:11)
    at resolvePackageEntry (file:///Users/ciaran/dev/archive28/node_modules/vite/dist/node/chunks/dep-75f53616.js:28682:9)
    at tryNodeResolve (file:///Users/ciaran/dev/archive28/node_modules/vite/dist/node/chunks/dep-75f53616.js:28423:20)
    at nodeImport (file:///Users/ciaran/dev/archive28/node_modules/vite/dist/node/chunks/dep-75f53616.js:55953:26)
    at ssrImport (file:///Users/ciaran/dev/archive28/node_modules/vite/dist/node/chunks/dep-75f53616.js:55863:30)
    at eval (/Users/ciaran/dev/archive28/src/routes/+page.svelte:4:37)
    at async instantiateModule (file:///Users/ciaran/dev/archive28/node_modules/vite/dist/node/chunks/dep-75f53616.js:55925:9)

Is there something obvious I've done wrong here?

Style doesn't reflect the changes if style changed

Map.svelte

<script lang="ts">
  import { MapLibre, ScaleControl } from 'svelte-maplibre';
  import LayerSelector from './components/layer-selector.svelte';
  import { Layers } from './store/layer.store';

  let styleURL: string;

  Layers.subscribe((value) => {
    styleURL = value.styleURL;
  });
</script>

<div>{styleURL}</div>

<MapLibre style={styleURL} class="relative h-screen w-full" attributionControl={false}>
  <LayerSelector />
  <ScaleControl />
</MapLibre>

layer.store.ts

import { writable } from 'svelte/store';
import { MAP_LAYERS } from '../constants';

export const Layers = writable(MAP_LAYERS[0]);

the url in web page as string is changing but the map doesn't change with that

Plumb through hash

When constructing the map, hash syncs the viewport to the URL. We could add this to MapLibre.svelte. But the center and zoom in maplibre's constructor override the initial URL state. So I usually set these two params, unless window.location.hash is defined. I'm not sure how hash in the component should work, because it looks like you're reacting to any changes to the zoom and center props:

$: if (center && !compare(center, $mapInstance?.getCenter())) $mapInstance?.panTo(center);

Maybe maplibre's own hash functionality should be ignored, and if this prop is set, something in the component could instead keep the URL fragment synced up?

Add a RasterLayer component

For our project we will need several layers of raster data. The most appropriate component for this seems to be Layer but its documentation states This is intended for use by other type-specific layer components, such as FillLayer, and usually you will want to use one of those in your code instead of directly using this component.

Are there plans to make a RasterLayer compoment?

Ability to override UI for existing controls

Specifically in the context of the GeolocateControl, as mentioned by @dominikg on the Svelte Discord.

Proposed method, which I haven't tried yet but I think should work ok:

Subclass the GeolocateControl class from maplibre, and override its onAdd function to call super.onAdd(map), but return a different DOM element managed by Svelte.

Then when your button is clicked, call the trigger method on the control to do the position update. You can also listen to events emitted by the control to perform other UI updates.

I think adding a slot into the GeolocateControl component, and checking for $$slots.default to enable the UI override functionality might be a good way to accomplish this.

Adding circles to start and end of polyline?

Hi there, thanks so much for your work on this lib!

Had a question and was quite stumped even after digging through docs. I have a bunch of coordinates on a map where I have drawing a line using the GeoJSON component:

{#each $routes as line, i}
	<GeoJSON id={line?.id} data={line?.polyline}>
		<LineLayer
			layout={{ 'line-cap': 'round', 'line-join': 'round' }}
			paint={{
				'line-width': 6,
				'line-color': '#316B72',
				'line-opacity': 0.8
			}}
		/>
	</GeoJSON>
{/each}

What I'm trying to do is to add a circle at the start and end of each polyline, and I can't figure out how to go about doing that? I tried adding a Circle component but it would just render a circle at every coordinate in the data of the polyline.

Any help would be much appreciated.

Sources not properly torn down

Discussed in #86

Originally posted by matthalstead October 17, 2023
I would like to be able to add and remove GeoJSON layers dynamically, initially I thought I could represent each of the layers I want to work with in either an array of layers or as specific explicitly defined layers within the <Maplibre> element.

e.g.

{#each data as d}
    <GeoJSON id="{d.id}" bind:data={d.layer} promoteId="fid">
        <FillLayer
        paint={{
          'fill-color': "red",
          'fill-opacity': 0.5,
        }}
      />
        <LineLayer
          layout={{ 'line-cap': 'round', 'line-join': 'round' }}
          paint={{ 'line-color': "black", 'line-width': 3 }}
        />
    </GeoJSON>
{/each}

or (which I am using at the moment - the following contains the whole script, data is provided by the parent through props)

<script>
    import { MapLibre, GeoJSON, LineLayer, FillLayer, Marker } from 'svelte-maplibre';
	
    export let data;
    export let center;
    let map;

    $: {
        console.log(data)
        console.log(map)
    }
    
    const lngLat = [174.863783, -36.871099]
</script>

<MapLibre
	bind:center={center}
	zoom={14}
	class="map"
    bind:map
	standardControls
	style="https://basemaps.cartocdn.com/gl/positron-gl-style/style.json">

    {#if data?.layer_1}
    <GeoJSON id="layer_1" bind:data={data.layer_1} promoteId="fid">
        <FillLayer
        paint={{
          'fill-color': "red",
          'fill-opacity': 0.5,
        }}
      />
        <LineLayer
          layout={{ 'line-cap': 'round', 'line-join': 'round' }}
          paint={{ 'line-color': "black", 'line-width': 3 }}
        />
    </GeoJSON>
    {/if}
    {#if data?.layer_2}
    <GeoJSON id="layer_2" bind:data={data.layer_2} promoteId="fid">
        <FillLayer
        paint={{
          'fill-color': "green",
          'fill-opacity': 0.5,
        }}
      />
        <LineLayer
          layout={{ 'line-cap': 'round', 'line-join': 'round' }}
          paint={{ 'line-color': "yellow", 'line-width': 3 }}
        />
    </GeoJSON>
    {/if}
    {#if data?.layer_3}
    <GeoJSON id="layer_3" bind:data={data.layer_3} promoteId="fid">
        <FillLayer
        paint={{
          'fill-color': "white",
          'fill-opacity': 0.4,
        }}
      />
        <LineLayer
          layout={{ 'line-cap': 'round', 'line-join': 'round' }}
          paint={{ 'line-color': "red", 'line-width': 3 }}
        />
    </GeoJSON>
    {/if}
</MapLibre>

<style>
	:global(.map) {
		height: 800px;
	}

	h1 {
		color: purple;
	}
</style>

This works ok for rendering them and then hiding them based on an update to the layers defined in data, but when I add one back in, e.g. set data.layer_1 back to a Geo JSON object, I get the following error:

style.ts:773 Uncaught (in promise) Error: Source "layer_1" already exists.
    at ae.addSource (style.ts:773:19)
    at Ua.Map.addSource (map.ts:1928:20)
    at $$self.$$.update (GeoJSON.svelte:22:10)
    at init (Component.js:144:5)
    at new GeoJSON (GeoJSON.svelte:58:31)
    at create_if_block_2 (Map.svelte:47:56)
    at Object.update [as p] (Map.svelte:46:25)
    at update_slot_base (utils.js:203:8)
    at Object.update [as p] (MapLibre.svelte:208:26)
    at Object.update [as p] (MapLibre.svelte:207:30)

I suspect that I need to remove GeoJSON layers through an API call instead to a bound map instance, but I don't seem to be able to find out how. Are there any examples of dynamically adding and removing layers?

Stop event propagation

Hello

First, thanks for this great library !

I have multiple layers placed on top of each other. I would like to stop event propagation.

Here is a demo of the problem : svelte-mapping-multi-layer-event

It draws a red square on top of a green square. When you click at the intersection of both squares, both events are triggered.

image

I have tried several methods like :

    event.stopImmediatePropagation()
    event.stopPropagation()
    event.preventDefault()
    event.detail.event.originalEvent.stopImmediatePropagation()
    event.detail.event.originalEvent.stopPropagation()

But nothing works.
Do you know how to achieve this ?

In svelte-leaflet, there is an option for this :

options={{ bubblingMouseEvents: false }}

Support layers only firing mouse events if it is the top-most layer for that event.

This gives more intuitive click behavior, since we often want clicks to only apply to the topmost layer which the user can actually see.

This can be done using map.queryRenderedFeatures on the event's point, and then the first result in the returned array is the top-most. Each layer than calls this and compares the first result with itself.

The primary challenge here is to only call this once per event, across all layers. It's potentially expensive to call this multiple times per event, and each call will always return the same results, so some way of caching the result should be present. I'm hoping there's a suitable key accessible to all the layers for an event that we can use in a WeakMap, but need to research a bit. Worst case we just cache keyed on event.point, keeping only the latest, and also autoclear after a tiny delay.

cf #45

Support easier methods of setting beforeId

Some common methods include placing layers below all built-in symbol layers and so on. We should support this kind of thing easily without having to make the user iterate over all the layers each time.

Phantom popup sometimes appears

If you use Popup openOn="hover" and quickly move your mouse to focus on a valid popup, sometimes it's possible to wind up with an empty popup:

screencast.mp4

I'm not sure why this happens yet or how to reliably reproduce it.

Add events to the map

Hello @dimfeld

I have more ideas 😊 !

I think it would be useful to add all the existing events on layer, on the map as well:

  • click
  • contextmenu
  • dblclick

Thanks

NPM Package?

This looks promising. Are there any plans to release it on npm?

Merge featureState from multiple sources

With the upcoming #65, we'll run into issues if multiple places are trying to setFeatureState on the same feature with different pieces of state. This could happen when JoinedData and a Layer with manageHoverState are both used.

The solution is probably to make a separate piece of code which can merge the hover state with other state. Then everything that currently calls $map.setFeatureState could call that code instead. While this could be made generic, it's probably simplest to just make it work with a hover value and one other optional state object.

Inconsistent use of manageHoverState

I spent a bit of time puzzling through how popups were triggered when hovering on a line layer, but my opacity wasn't changing:

          <LineLayer
            paint={{
              "line-width": 5,
              "line-color": "red",
              "line-opacity": hoverStateFilter(1.0, 0.5),
            }}
          > 
            <Popup openOn="hover" let:features>
              ...
            </Popup>
          </LineLayer>

I needed to pass manageHoverState. FillLayer and others don't override this, or even let it be set, AFAICT.

export let manageHoverState = false;

export let manageHoverState = true;

Any reason for line layer to disable it by default? For consistency, what if all the layers did export let manageHoverState = false; to let it be overridable? Happy to send a PR if that's the desired behavior.


And thanks by the way for this library! I've been using MapLibre and Svelte for a few months in different projects, and by far this library is looking extremely promising for simplifying some of them. I'll open a few other issues, and I'm happy to send PRs.

Popup no longer closes on the marker layer

Hi there,

since version 5.0.0 the popup (openOn='click') does not close anymore when clicking on a marker from a MarkerLayer but remains open when clicking next to or into the popup. If further markers are clicked, all respective popups remain open. With openOn='hover' the problem does not exist.

To reproduce the problem, multiple markers can be clicked in the 'Custom Markers and Clusters' example.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.