dimfeld / svelte-maplibre

Svelte bindings for the MapLibre mapping library
https://svelte-maplibre.vercel.app
MIT License
318 stars 39 forks source link

flyTo doesn't properly center to the LngLat value of the marker #165

Closed thijserven closed 4 months ago

thijserven commented 4 months ago

I'm trying to get the map to zoom in to level 14 when clicking on markers with a home icon. I have implemented it like this:

    <MarkerLayer
        applyToClusters={false}
        asButton
        on:click={(event) => {
            event.detail.map.flyTo({
                center: event.detail.marker.getLngLat(),
                zoom: 14
            });
        }}
    >
            <div class="rounded-full border bg-background px-2 py-1 shadow"><Icon icon="mdi:home" /></div>
    </MarkerLayer>

But for some reason when I zoom in from level 0 to level 14 in one go (which is a big zoom distance) the marker ends up off-center. I've added a video which demonstrates the issue. Only when I click the marker again after the initial flyTo has completed, the map centers itself correctly around the marker.

https://github.com/dimfeld/svelte-maplibre/assets/51750966/d4777eb2-cc5d-4c8e-b783-fbd62a545c1c

Could this be a bug? Or am I implementing something wrong?

dimfeld commented 4 months ago

Hmm... that should work fine. I think there are 2 possibilties:

  1. Maplibre itself is just imprecise when you do this
  2. svelte-maplibre is interfering somehow

I'll take a look and get back to you on what I discover.

dimfeld commented 4 months ago

So it looks like the issue here is that MarkerLayer uses map.querySourceFeatures to get the features that it should be looking at, and there's some precision loss on the data that comes back from this. The returned coordinates also change a bit depending on how far I'm zoomed in; feels like something related to how the coordinates are represented for the WebGL context but I'm kind of guessing.

Here's some output from a plain JS example I created just to make sure that I wasn't doing something weird:

CleanShot 2024-05-15 at 11 47 01

One thing you could probably do is to use the ID of the feature (at event.detail.feature.id) that comes with the event to look up the feature in the original data and flyTo the exact coordinates there instead.

Unfortunately I don't think there's much I can do about this in the library itself, though let me know if you come up with some idea.

thijserven commented 4 months ago

@dimfeld Thanks for your response and your help! I went with your suggestion of using the feature id to get the original coordinates and it worked! The flyTo() function now centers the map correctly around the selected marker.

For anyone encountering the same problem in the future, here is my simplified implementation on how I fixed this issue:

<script lang="ts">
  function getOriginalFeature(feature: GeoJSON.Feature): GeoJSON.Feature | undefined {
    if (data.type !== 'FeatureCollection') return undefined;
    return data.features.find((f) => f.properties?.id === feature.properties?.id);
  }
</script>

<MarkerLayer
  applyToClusters={false}
  asButton
  on:click={(event) => {
    const originalFeature = getOriginalFeature(event.detail.feature);
    if (!originalFeature || originalFeature.geometry.type !== 'Point') return;

    event.detail.map.flyTo({
      center: [originalFeature.geometry.coordinates[0], originalFeature.geometry.coordinates[1]],
      zoom: 14
    });
  }}
>
  <div class="rounded-full border bg-background px-2 py-1 shadow"><Icon icon="mdi:home" /></div>
</MarkerLayer>
dimfeld commented 4 months ago

Glad to hear it!