Select to view content in your preferred language

Getting GoTo Interrupted when using zoomTo

621
1
08-23-2023 07:55 AM
kprenesti
New Contributor II

Hello all.  I have a Vue project in which we are plotting markers shaped as pins.  Part of the requirement is that the markers must point to the location rather than be centered on top of it.  So we're using an offset to reposition the markers and then to reposition the popup so it's on the top center of the marker.  On initial click of the marker, everything works fine, but when I try to zoom to the marker, the map zooms, but then gets the following error: 

aA {name: 'view:goto-interrupted', details: undefined, message: 'Goto was interrupted'}
details: undefined
message: "Goto was interrupted"
name: "view:goto-interrupted"
[[Prototype]]: oA

I have tried to solve it in a few different ways, but this is the current method.  Can anyone help me figure out what's going wrong?  (Or if there's a better way to achieve my objective as a whole...) Here's the code: 

<!-- eslint-disable no-console -->
<template>
    <div
        id="map"
        style="width: 100%;"
    />
</template>

<script>
import Map from '@arcgis/core/Map';
import MapView from '@arcgis/core/views/MapView';
import GraphicsLayer from '@arcgis/core/layers/GraphicsLayer';
import CustomContent from '@arcgis/core/popup/content/CustomContent';
import * as reactiveUtils from '@arcgis/core/core/reactiveUtils';
import Graphic from '@arcgis/core/Graphic';
import Point from '@arcgis/core/geometry/Point';
import { createVNode, render } from 'vue';

import MarkerSVG from '../../assets/marker.svg?url';

import PopupTemplateHeader from './PopupTemplateHeader.vue';
import PopupTemplateContent from './PopupTemplateContent.vue';

export default {
    data() {
        return ({
            selectedMarkers: [],
            animationState: ''
        });
    },
    watch: {
        '$store.list.data.records': {
            handler() {
                this.$nextTick(this.plotRecords);
            },
            immediate: true
        }
    },
    created() {
        this.$mitt.on('open-popup', (id) => {
            const graphic = this.layer.graphics.items.find((i) => i.attributes.record.id === id);
            this.openPopup([graphic]);
        });
        this.$mitt.on('close-popup', this.clearPopup);
    },
    mounted() {
        window.MapComponent = this;

        this.map = new Map({
            basemap: 'streets-navigation-vector',
            customParameters: { type: 'map' }
        });

        this.view = new MapView({
            map: this.map,
            popup: {
                collapseEnabled: false,
                dockOptions: {
                    buttonEnabled: false
                },
                highlightEnabled: false,
                alignment: 'top-center',
                viewModel: {
                    autoCloseEnabled: false,
                    includeDefaultActions: true
                }
            },
            center: [-98.805, 38.027], // lng, lat
            zoom: 4,
            container: 'map',
            constraints: {
                minZoom: 2,
                maxZoom: 15,
                rotationEnabled: false
            }
        });

        // Move zoom toggles to bottom right
        this.view.ui.move(['zoom'], 'bottom-right');

        this.view.on('click', (event) => {
            // do not let Esri handle the click event
            event.stopPropagation();

            this.view.hitTest(event).then(({ results }) => {
                const markers = results.filter((r) => r.graphic.symbol?.type === 'picture-marker').map((f) => f.graphic);
                if (markers.length) {
                    this.openPopup(markers);
                } else {
                    this.clearPopup();
                }
            });
        });

        reactiveUtils.on(
            () => this.view.popup,
            "trigger-action",
            (event) => {
                if (event.action.id === 'zoom-to-feature') {
                    this.openPopup([this.view.popup.selectedFeature], true);
                }
                else {
                    console.log('EVENT ID: ', event.action.id);
                    console.log('EVENT: ', event);
                    console.log('GEOMETRY: ', this.view.popup.selectedFeature.geometry);
                }
            });

        // whenever the popup title changes, render the custom header content and inject it
        reactiveUtils.when(() => this.view.popup?.title, () => {
            const interval = setInterval(() => {
                const container = this.$el.querySelector(`div[id="${this.view.popup.titleId}"] .maps-popup-header`);

                if (container) {
                    const recordId = container.classList[1];
                    const record = this.$store.list.data.records.find((r) => r.id === recordId);

                    if (record) {
                        const header = createVNode(PopupTemplateHeader, { columns: this.$store.list.info.displayColumns, record, addressFieldsArray: this.$store.addressFieldsArray });
                        render(header, document.createElement('div'));
                        container.innerHTML = header.el.innerHTML;
                    }

                    clearInterval(interval);
                }
            }, 10);
        });

        reactiveUtils.when(() => this.view.animation, (animation) => {
                this.animationState = animation.state; // Should be 'running'
                animation.when((animation) => {
                    this.animationState = animation.state; // Should be 'finished'
                })
                .catch((animation) => {
                    this.animationState = animation.state; // should be 'stopped'
                });
        });
    },
    methods: {
        async openPopup(markers, zoomIn = false) {
            // clear any existing popup
            if (this.view.popup.visible) {
                this.clearPopup();
            }
            
            // pan the map center to the first marker
            this.panTo(markers[0].geometry, zoomIn);

            markers.forEach((marker) => {
                const { record } = marker.attributes;
                const recordTitle = record.fields?.Name?.displayValue ?? record.fields?.Name?.value ?? '';
                marker.popupTemplate = {
                    title: `<div class="maps-popup-header ${record.id}">${recordTitle}</div>`,
                    content: [new CustomContent({
                        creator: () => {
                            // build content
                            const content = createVNode(PopupTemplateContent, { columns: this.$store.list.info.displayColumns, record });
                            render(content, document.createElement('div'));
                            return content.el;
                        }
                    })],
                    overwriteActions: true,
                    actions: [
                        {
                            id: 'zoom-to-feature',
                            className: 'esri-icon-zoom-in-magnifying-glass',
                            icon: 'magnifying-glass-plus',
                            type: 'button',
                            visible: true,
                            title: "{messages.zoom}"                   
                        }
                    ]
                };
            });
            this.selectedMarkers = [ ...markers ];
            const location = this.createOffsetPoint(markers[0].geometry);
            if (this.animationState === 'finished') {
                this.view.openPopup({
                    features: markers,
                    location
                });
            }
            
        },
        clearPopup() {
            this.view?.closePopup();

            if (this.view.popup.clear) {
                this.view.popup.features.forEach((feature) => {
                    delete feature.popupTemplate;
                });

                this.view.popup.clear();
            }
        },
        async panTo(target, zoomIn) {
            const offset = this.createOffsetPoint(target, 'zoom');
            const zoom = zoomIn ? this.view.zoom + 4 : this.view.zoom;

            // use modified extent center as target
            return await this.view.goTo(
                { geometry: offset, zoom },
                { duration: 750 }
            )
        },
        plotRecords() {
            // clear any existing popups before plotting
            this.clearPopup();

            if (this.layer) {
                this.map.layers.remove(this.layer);
            }
            const latField = this.$store.latLongFields.lat;
            const longField = this.$store.latLongFields.long;
            const graphics = this.$store.list.data.records?.reduce((arr, record) => {
                if (this.$store.hasGeo(record)) {
                    arr.push(
                        new Graphic({
                            attributes: { record },
                            geometry: {
                                type: 'point', // autocasts as new Point()
                                longitude: record.fields[longField].value,
                                latitude: record.fields[latField].value
                            },
                            symbol: {
                                type: 'picture-marker', // autocasts as new SimpleMarkerSymbol()
                                url: import.meta.env.MODE === 'development' ? `https://localhost:5173${MarkerSVG}` : MarkerSVG,
                                height: 26,
                                width: 20,
                                yoffset: 13
                            }
                        })
                    );
                }

                return arr;
            }, []);

            if (graphics?.length) {
                this.layer = new GraphicsLayer({ graphics });
                this.map.layers.add(this.layer);
                this.view.goTo(graphics);
            }
        },
        createOffsetPoint(recordPoint, type = 'popup') {
            // Convert to screen point to get location of x and y in pixels
            const screenPoint = this.view.toScreen(recordPoint);
            // Subtract 26 to offset the marker and its offset
            screenPoint.y =  type === 'popup' ? screenPoint.y - 26 : screenPoint.y - 100;
            // Convert offset screenpoint to regular point to use in Graphic.
            return this.view.toMap({ ...screenPoint });
        }
    }
};
</script>

<style>
    .esri-popup__main-container {
        border-radius: 5px;
    }

    .esri-popup__header {
        border-bottom: 1px solid #dddd;
        margin-bottom: 10px;
        padding-top: 3px;
    }

    .esri-popup__button:hover {
        background-color: unset;
    }

    .esri-popup__content {
        margin-bottom: 0;
    }

    .esri-feature__content-element {
        margin-bottom: 0 !important;
    }

    .esri-popup--aligned-top-center .esri-popup__pointer-direction, .esri-popup--aligned-bottom-center .esri-popup__pointer-direction {
        transform: scale(1, 1.1) rotate(45deg) !important;
    }
</style>
0 Kudos
1 Reply
JeffreyThompson2
MVP Regular Contributor

I have not worked in Vue, so I am going to use React terms. Most likely your issue is caused by altering the state during the goTo animation causing the component to reload. Does that make sense in a Vue context?

GIS Developer
City of Arlington, Texas
0 Kudos