vinayakkulkarni / v-mapbox

Vue-ish way for interacting with map(box|libre)-gl-js 🧭
https://v-mapbox.netlify.app/
MIT License
185 stars 45 forks source link

Geojson layer not clearing (Removal Not noticing) #463

Open InhyeLee-Data opened 3 years ago

InhyeLee-Data commented 3 years ago

Hi! I'm on a nuxt app with a vue-mapbox wrapper.

"mapbox-gl": "^1.13.1",
"nuxt": "^2.14.6",
"v-mapbox": "^1.7.3"

One interesting issue I have right now is that a geojson layer that is removed in code still pertains to exist in the rendered page.

This is the scenario.

  1. I have one component that is supposed to display one layer at a time. (let's say one circle)
  2. I click on a button to change the layer into a different one.
  3. In chrome Vue devtools, the reflects the changed layer and only shows one layer.
  4. However, on the rendered mapbox, I can visibly see two layers (let's say two circles) appearing simultaneously.
  5. The layers are also individually clickable, if I were to create a pop up on click.

Anybody has an idea why this would happen and how I may be able to work through this?

vinayakkulkarni commented 3 years ago

Hey @InhyeLee-Data, can you please create a fork of this codesandbox or this codesandbox & provide a reproducible example?

stefangeorg commented 1 year ago

I'm having the same issue: ` `

<script> window.global = window; import { VMap, VControlNavigation, VLayerMapboxGeojson } from "v-mapbox"; import { reactive } from "vue";

function readFileAsync(file) { return new Promise((resolve, reject) => { let reader = new FileReader();

reader.onload = () => {
  resolve(reader.result);
};

reader.onerror = reject;

reader.readAsArrayBuffer(file);

}) }

export default { name: "App", components: { VMap, VLayerMapboxGeojson, }, setup() { const state = reactive({ map: { accessToken: "pk.eyJ1Ijoic29jaWFsZXhwbG9yZXIiLCJhIjoiREFQbXBISSJ9.dwFTwfSaWsHvktHrRtpydQ", style: "mapbox://styles/mapbox/streets-v11?optimize=true", // style: "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json", center: [13.4, 52.5], zoom: 11, maxZoom: 22, crossSourceCollisions: false, failIfMajorPerformanceCaveat: false, attributionControl: false, preserveDrawingBuffer: true, hash: false, minPitch: 0, maxPitch: 60, }, });

return {
  state,
  files: reactive([]),
  layers: reactive([]),
};

}, methods: { convertFileToLayers() { this.files.forEach(file => { try{ file.handler(file.file); } catch(e) { console.error(e) } }) this.files = reactive([]); console.log(this.files, this.files.length) }, onFileAdd(file) { this.files.push(file); console.log(file); this.detectFileType(file); }, detectFileType(file) { const fileTypes = { 'application/zip': [{display: 'Compressed Shapefile', handler: this.shapeFileHandler}] } const extensions = { 'geojson': [{display: 'Geojson layer', handler: this.geojsonHandler}] } const splitted = file.file.name.split('.'); const extension = splitted[splitted.length - 1] console.log(extension) if (file.file.type in fileTypes) { const type = fileTypes[file.file.type] file.file.options = type } if (extension in extensions){ file.file.options = extensions[extension]; }

}, 
shapeFileHandler(file) {
    console.log('called handler', file)
    var reader = new FileReader();
    // convert shapefile to geojson
    reader.onloadend = (e) => {
        console.log(e.target.result)
        shp(e.target.result).then((geojson) => {
            //see bellow for whats here this internally call shp.parseZip()
            this.layers.push({
                source: {'type': 'geojson', data: geojson},
                id: file.name,
                active: true,
                type: "fill",
                paint: {
                    "fill-color": 'red',
                    "fill-opacity": 0.4,
                },

            })
        });
    };
    reader.onerror = function (e) {
        reject(e.target.error);
    };
    reader.readAsArrayBuffer(file);

},
async geojsonHandler(file) {
    let contentBuffer = await readFileAsync(file);
    const decoder = new TextDecoder('utf-8');
    const data = JSON.parse(decoder.decode(contentBuffer))
    console.log(data);

    this.layers.push({
        source: {'type': 'geojson', data: data},
        id: file.name,
        active: true,
        type: "fill",
        paint: {
            "fill-color": 'blue',
            "fill-opacity": 0.4,
        },

    })
}

}, computed: { activeLayers() { return this.layers.filter(i => i.active) } } }; `

<style lang="scss"> @import "mapbox-gl/dist/mapbox-gl.css"; @import "v-mapbox/dist/v-mapbox.css"; html, body { margin: 0; }

app {

font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; } .w-screen { width: 100vw; } .h-screen { height: 100vh; } .h-full { height: 100%; } .w-full { width: 100%; }

app .show {

display: block;

}

map {

position: relative;

}

layers {

position: absolute;
bottom: 5%;
left: 5%;
width: 200px;
z-index: 100000;
background-color: white;

} `

I'm allowing users to drag a shapefile or geojson onto the page. Then I show it. But they can deactivate a layer, and it is removed from the Vue inspector, but remains active on the map. Seems no proper destruction is happening when a layer is removed.

vinayakkulkarni commented 1 year ago

@stefangeorg can you please create a sandbox for better debugging ?

stefangeorg commented 1 year ago

@vinayakkulkarni I'll try to setup one, but its relatively simple actually. There is no onUnmounted action. So if the VLayerMapboxGeojson is removed from the parent, the layers and source remain on the map eventhough the component is no longer there. We should cleanup on unMounted to remove the layer and source (or have an options to keep the source but remove the layer)

stefangeorg commented 1 year ago

Here is how I solved it By extending your component, and passing in the map object as a property. `

export default defineComponent({ ...VLayerMapboxGeojson, ...{ name: "GeoJSONLayer", extends: VLayerMapboxGeojson, props: { ...VLayerMapboxGeojson.props, ...{ mapRef: { type: Object, required: true, }, }, }, mounted() {}, unmounted() { if (!this.mapRef) { return; } if (this.mapRef.getLayer(this.layerId)) { this.mapRef.removeLayer(this.layerId); } if (this.mapRef.getSource(this.sourceId)) { this.mapRef.removeSource(this.sourceId); } }, }, }); `

<GeoJSONLayer v-for="(layer, i) in activeLayers" :key="i" :sourceId="layer.id" :source="layer.source" :layerId="layer.id" :layer="layer" :mapRef="state.mapRef" />

If I inactivate a layer, then it will get removed from the map.