Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Failed to invert matrix error #6486

Closed
rulyotano opened this issue Apr 9, 2018 · 20 comments
Closed

Failed to invert matrix error #6486

rulyotano opened this issue Apr 9, 2018 · 20 comments

Comments

@rulyotano
Copy link

The error still in version v0.44.2.

If it helps, I also get the same error on random occasions when making zoom in/out by using mouse scroll for instance. First is raised a failed to invert matrix error, and then, on every action in the map, for instance moving the mouse over, is raised this error: Invalid LngLat object: (NaN, NaN). Call Stack for this last error:

lng_lat.js:29 Uncaught Error: Invalid LngLat object: (NaN, NaN)
    at new LngLat (lng_lat.js:29)
    at Transform.coordinateLocation (transform.js:352)
    at Transform.pointLocation (transform.js:329)
    at e.unproject (map.js:559)
    at h (bind_handlers.js:155)
    at HTMLDivElement.a (bind_handlers.js:81)

Seems that the problem in this last error is when trying to convert the point to coordinate, in the line return this.coordinateLocation(this.pointCoordinate(p)) the this.pointCoordinate(p) is returning Coordinate {column: NaN, row: NaN, zoom: NaN}. It is difficult to reproduce the error, because it is random, if any new detail I will update this post or create a new one.

Details of failed to invert matrix error:

Uncaught Error: failed to invert matrix
    at Transform._calcMatrices (transform.js:549)
    at Transform.prototypeAccessors.zoom.set (transform.js:165)
    at ScrollZoomHandler._onScrollFrame (scroll_zoom.js:237)
    at e.i._updateCamera (camera.js:901)
    at e._render (map.js:1464)
    at map.js:1567

The line:

    // inverse matrix for conversion from screen coordinaes to location
    m = mat4.invert(new Float64Array(16), this.pixelMatrix);
    if (!m) throw new Error("failed to invert matrix");
    this.pixelMatrixInverse = m;

Question, what about if this.pixelMatrix isn't invertible? Has that any logic?

@mollymerp
Copy link
Contributor

Hi @rulyotano, sorry to hear you're running into a problem. does this happen randomly for you on mapbox examples, or in application code you've written? If the latter, can you try and create a minimal, self-contained (no 3rd party libraries or code unrelated to reproducing the map error) example on jsfiddle or jsbin?

@rulyotano
Copy link
Author

Hi @mollymerp, in response to you: does this happen randomly for you on mapbox examples, or in application code you've written?: it happens randomly in code that I have written I'm also using a custom style. Why do I think it is an issue?: 1) It happens randomly, 2) Happens when making zoom in/out. I have doesn't seen the error on examples, I will try to reproduce. Thanks

@andrewharvey
Copy link
Collaborator

It might not be the same issue but I can get an error like this by doing very rapid map panning/pitching/rotating when zoomed out as much as possible similar to what you describe.

Uncaught Error: Invalid LngLat object: (122330.91065462514, NaN)
    at new LngLat (lng_lat.js:29)
    at Transform.unproject (transform.js:272)
    at camera.js:589
    at e._onFrame (camera.js:886)
    at e.i._updateCamera (camera.js:901)
    at e._render (map.js:1464)
    at map.js:1567

Mind you it doesn't cause me any noticeable issues as a user. @rulyotano do you notice this actually causes any issues to the user?

@rulyotano
Copy link
Author

@andrewharvey that is the main problem, it is a big issue for the user, because stops the complete map functionality, it stops working. Even the entire webapp can break due to micro-overprocessing. The Invalid LngLat object it is a consequence, it happens every time you move the mouse by over the map, for instance, the main error is the invert matrix one.

It is very difficult to me to reproduce the issue, even in my code, it happens just a very few times, and, I think, is randomly.

I don't know if it happens when chrome is very memory loaded, or after making a map rotate, etc. What I would expect, is that if some error like that happens, it doesn't block the entire map.

@andrewharvey
Copy link
Collaborator

Your bug must be different to the one I saw, since for me the map still works fine even after the error.

Without identifying the exact cause I wonder if we should band-aid it by just being more proactive in checking function arguments look reasonable all the way up your stacktrace?

@rulyotano
Copy link
Author

I got the error again, but this time with an complete empty map, only by making zoom in/out. I'm going to paste the the code of my Map.js class, but again, the map was really empty, no extra-layers, no componetns, no click events, nothing.

import mapboxgl from 'mapbox-gl';
import {head, get, isEmpty, isFunction, some, find, findIndex, setWith} from 'lodash';
import config from '../../../config/config';

const SATELLITE_LAYER_ID = "mapbox-mapbox-satellite";

const availableTypes = [
    { key: "streets", langKey: "Map" },
    { key: "satellite", langKey: "Satellite" }
];

export default class Map {
    constructor(element, center = config.MAP_DEFAULT_CENTER) {
        mapboxgl.accessToken = config.MAPBOX_TOKEN;
        this._selectedMapType = head(availableTypes);
        this._map = new mapboxgl.Map({
            container: element,
            style: 'mapbox://styles/solinftec-dev/cjasfb7cpj6qo2rn0u09jl604?optimize=true',
            center: center,
            zoom: 4
        });
        this._popup = null;

        this._onLoad = new Promise((resolve, reject) => {

            /**custom click handler for implementing stop propagation on click event. Issue that
             * mapbox-js-lg doesnt' implement.
            */
            this._map.on('click', (e) => {
                //iterates layers, first latest added
                for (let i = this._layers.length - 1; i >= 0; i--) {
                    const layer = this._layers[i].key;                            
                    const layerEventListeners = get(this._eventsListeners, `["click"][${layer}]`);
                    //check if have event listeners
                    if (layerEventListeners && !isEmpty(layerEventListeners)) {                                
                        //check if clicked (have features in the point)
                        const features = this._map.getLayer(layer) ? this._map.queryRenderedFeatures(e.point, {layers: [layer]}) : [];
                        if (!isEmpty(features)){
                            const eExtended = { ...e, features };
                            //call all listeners with the new event arg
                            for (let j = layerEventListeners.length - 1; j >= 0; j--) {
                                const listener = layerEventListeners[j];
                                if (isFunction(listener)){
                                    const result = listener(eExtended);
                                    //if listener stop propagation returns
                                    if (result === false)
                                        return;
                                }
                            }
                        }
                    }
                }
            });

            this._map.on('load', () => {
                resolve(this);
            });

            this._map.on('error', () => {
                reject("error loading mapbox");
            });

            this._lastPoint = null;
            this._lastLngLat = null;
            this._map.on('mousemove', e => {
                this._lastPoint = e.point;
                this._lastLngLat = e.lngLat;
            });
        });

        this._layers = [];
        this._onPopupCloseCallbacks = [];
        this._eventsListeners = {} //object with the scrtucture { 'event-type': { 'layer-id': [...listeners...] },... }
    }

    //#region Mouse Position
    /**Get the last mouse point, it is a point of pixels, relative to the pc screen. */
    get mouseLastPoint(){
        return this._lastPoint;
    }

    /**Get the last mouse lng lat coordinates. */            
    get mouseLastLngLat(){
        return this._lastLngLat;
    }
    //#endregion

    //#region Map Types
    get selectedMapType(){
        return this._selectedMapType;
    }

    static get allMapTypes(){
        return availableTypes;
    }

    setMapType(key){
        if (key !== this.selectedMapType.key && some(availableTypes, t=>t.key === key)){
            this._selectedMapType = find(availableTypes, t=>t.key === key);
            this.map.setLayoutProperty(SATELLITE_LAYER_ID, 'visibility',
                this._selectedMapType.key === 'streets' ? 'none' : 'visible');
        }
    }
    //#endregion

    /**Return the map instance (in this case, mapbox map instance)*/
    get map() {
        return this._map;
    }


    //#region Popups
    get popup(){
        return this._popup;
    }

    /**Action for opening a popup */
    openPopup(lngLat, text = null, html = null, domContent = null){
        this.closePopup();
        this._popup = new mapboxgl.Popup();
        this._popup.setLngLat(lngLat);
        if (text)
            this._popup.setText(text);
        else if (html)
            this._popup.setHTML(html);
        else if (domContent)
            this._popup.setDOMContent(domContent);
        this._popup.addTo(this._map);
        this._popup.on('close', 
            () => this._onPopupCloseCallbacks.forEach(callback=>isFunction(callback) && callback()));
    }

    /**Action for close a popup */
    closePopup(){
        if (this._popup){
            this._popup.remove();
            this._popup = null;
        }
    }

    /**Register a callback to the Pupup.on('close') event.
     * @returns {function} - returns an unregister function
     */
    onPopupClose(callback){
        this._onPopupCloseCallbacks.push(callback);
        return () => {
            let itemIdex = this._onPopupCloseCallbacks.indexOf(callback);
            if (itemIdex >= 0)
                this._onPopupCloseCallbacks.splice(itemIdex, 1);
        }
    }

    //#endregion

    /**Promise that is resolved when map is loaded*/
    get onLoad() {
        return this._onLoad;
    }

    //#region Layers
    /**MapLayerBase collection, in format: {key: MapLayerBase}, all layers of this map*/
    get layers() {
        return this._layers;
    }

    /**Add layer,
     * @param {string} before - Layer id to insert the new one before
     * @return {Promise}*/
    addLayer(layer, before = null) {
        if (!layer)
            return new Promise((resolve, reject)=>reject("no layer"));
        if (before){                    
            const insertIndex = findIndex(this._layers, it=>it.key === before);
            this._layers.splice(insertIndex, 0, layer);
        } else
            this._layers.push(layer);
        return layer.mapAdd(this, before);
    }

    removeLayer(layer){
        if (!layer)
            return;
        this._layers = this._layers.filter(l=>l.key === layer.key);
        layer.mapRemove(this);
    }

    showLayer(layer){
        if (!layer)
            return;
        layer.mapShow(this);
        layer.show = true;

        //recursively show children layers
        if (!isEmpty(layer.childrenLayers))
            layer.childrenLayers.forEach(l=>this.showLayer(l))
    }

    hideLayer(layer){
        if (!layer)
            return;
        layer.mapHide(this);
        layer.show = false;

        //recursively hide children layers
        if (!isEmpty(layer.childrenLayers))
            layer.childrenLayers.forEach(l=>this.hideLayer(l))
    }
    //#endregion

    //#region Events

    /**Add a new listener to map events with stop propagation
     * @argument {string} type - event type eg. 'click'
     * @argument {string?} layer - layer id or key (this argument is optional)
     * @argument {Function} listener - event listener
      */
    on(type, layer, listener){
        //only for listeners that have layers
        if (arguments.length === 2){
            listener = layer;
            return this._map.on(type, listener);
        }
        if (type !== 'click')
            return this._map.on(type, layer, listener);
        let layerEventListeners = get(this._eventsListeners, `[${type}][${layer}]`);
        if (!layerEventListeners)
        {
            layerEventListeners = [];
            setWith(this._eventsListeners, `[${type}][${layer}]`, layerEventListeners, Object);                  
        }
        layerEventListeners.push(listener);
    }

    /**Remove listener from map events with stop propagation
     * @argument {string} type - event type eg. 'click'
     * @argument {string?} layer - layer id or key (this argument is optional)
     * @argument {Function} listener - event listener
      */
    off(type, layer, listener){              
        //only for listeners that have layers  
        if (arguments.length === 2){
            listener = layer;
            return this._map.off(type, listener);
        }
        if (type !== 'click')
            return this._map.off(type, layer, listener);

        let layerEventListeners = get(this._eventsListeners, `[${type}][${layer}]`);
        if (layerEventListeners){
            layerEventListeners = layerEventListeners.filter(it => it!==listener);
            setWith(this._eventsListeners, `[${type}][${layer}]`, layerEventListeners, Object);
        }
    }

    //#endregion

    remove(){
        if (this._map){
            this._map.remove();
            this._map = null;
        }
    }

    updateSource(sourceId, data){
        const source = this._map.getSource(sourceId);
        if (source)
            source.setData(data);
        else{
            this._map.addSource(sourceId, data)
        }
    }
}

@mollymerp
Copy link
Contributor

@rulyotano thanks for following up! can you put the empty map example that reproduced the error on jsbin or jsfiddle please?

@rulyotano
Copy link
Author

@mollymerp I will try, but I repeat, it is raised very randomly.

@andrewharvey
Copy link
Collaborator

I concur, as I mentioned I see the same error on even the simple map example, so it's nothing from the application code causing this. One option would to try to be more defensive, ie. check inputs to each function match what is expected, but this might decrease performance, so it might be best to try to identify the exact source of the error.

@hyperknot
Copy link

hyperknot commented May 23, 2018

I'm getting the exact same error in Sentry reports, with 0.45.0.

One line of

failed to invert matrix

And then countless:

Invalid LngLat object: (NaN, NaN)

I have no idea how to reproduce it, as I'm only seeing this via Sentry reports.

@hyperknot
Copy link

Here is a non-minified Sentry trace about this issue. All of them look the same, it's something with mouse wheel zoom calculation.
https://sentry.io/share/issue/4e1ac7a8383342e7a09dcf6dfa25af28/

I think what is happening is that some calculation makes the target value to be NaN. At least it is possible to trigger this issue by setting zoom to NaN on this line:

tr.zoom = interpolate(this._startZoom, this._targetZoom, k);

Thus I guess a very simple and safe fix would be to check if the interpolated value is NaN or not.

@hyperknot
Copy link

hyperknot commented May 24, 2018

Yes, looking more at the code, if any of t, k, or z end up being NaN, which can happen if any of their input is NaN, then it propagates to zoom.

What do you think about simply making:

const z = interpolate(this._startZoom, this._targetZoom, k)
if (!Number.isFinite(z)) return
tr.zoom = z;

@rulyotano
Copy link
Author

rulyotano commented May 24, 2018 via email

@hyperknot
Copy link

Also, an other bug, in the same lines. this._easing might be missing, terminating in

this._easing is not a function

https://sentry.io/share/issue/0d51616d29b04425a3dca5b4a77a4916/

For me it seems logical that if if (this._delta !== 0) didn't run then _easing will be missing.

@hyperknot
Copy link

Yes, the most trivial way scrolling can get into illegal matrix is via these 3 lines:

if (this._type === 'wheel') {
this._startZoom = tr.zoom;
this._easing = this._smoothOutEasing(200);
}

  1. If for some reason those 3 lines are not run first, interpolate returns null
  2. The Math.min(Math.max(zoom, this.minZoom), this.maxZoom); protection is not working for setting zoom somehow, and it goes through.
  3. calcMatrices has:
pixelMatrix | {"11":null,"10":null,"13":null,"12":null,"15":null,"14":null,"1":0,"0":1042.5,"3":0,"2":0,"5":1042.5,"4":0,"7":0,"6":0,"9":null,"8":null}
angle | 0
_pitch | 0
worldSize | null
width | 1236
_fov | 0.6435011087932844
center | {"lat":-27.18402617068871,"lng":-48.90232538367093}
cameraToCenterDistance | 1042.5
height | 695
projMatrix | {"11":null,"10":null,"13":null,"12":null,"15":null,"14":null,"1":0,"0":1.6868932038834952,"3":0,"2":0,"5":-3,"4":0,"7":0,"6":0,"9":null,"8":null}
y | null
x | null
verticalScale | null
farZ | 1052.925

@hyperknot
Copy link

@anandthakker

anandthakker pushed a commit that referenced this issue Jul 6, 2018
anandthakker added a commit that referenced this issue Jul 6, 2018
* Add failing test for #6782

* Fix ScrollZoom handler setting tr.zoom = NaN

Closes #6782
Closes #6486
Replaces #6921
pirxpilot added a commit to pirxpilot/mapbox-gl-js that referenced this issue Aug 9, 2018
@yangjnMapBox
Copy link

I'm getting the exact same error in Sentry reports, with 1.2.0.
'''
Uncaught Error: failed to invert matrix
at co._calcMatrices (transform.js:589)
at co.uo.zoom.set (transform.js:176)
at mo._onScrollFrame (scroll_zoom.js:279)
at Do.run (task_queue.js:52)
at r._render (map.js:1712)
at map.js:1837
'''
"map.scrollZoom.setWheelZoomRate({wheelZoomRate:0.1});" then when I zoom in or out ,the error
appear,and map can't zoom in or out.

@iwanmcm
Copy link

iwanmcm commented Oct 22, 2019

I was getting the same error "Uncaught Error: failed to invert matrix".

It turned out to be a syntax mistake from my side while calculating new zoom level:

var newZoom = $scope.map.getZoom() < 16 ? 16 : $scope.map.getZoom;

This line caused the newZoom to be invalid, since brackets were missing:

var newZoom = $scope.map.getZoom() < 16 ? 16 : $scope.map.getZoom();

This line works fine.

Just in case, someone has similiar code and might have made the same mistake.

@mlfarrelly
Copy link

mlfarrelly commented Jun 8, 2020

I was getting this error when trying to implement point clusters per this tutorial:

https://docs.mapbox.com/mapbox-gl-js/example/cluster/

I solved the problem by adding a check for an undefined zoom param here:

this.map.getSource('recordPoints').getClusterExpansionZoom(
          clusterId,
          (err, zoom) => {
            if (err) return
            if (!zoom) return

          this.map.easeTo({
            center: features[0].geometry.coordinates,
            zoom: zoom
          })
          }
        )
      })

@openui54u
Copy link

After may code changes, adding corrrect sequence in Authorisation for gps, deviceorientation etc I made the above code change in checking on !isNaN on the zoom in map.easeTo .... looks like the endless NaN error of InvertMatrix etc is gone.... thanks for the hint !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants