Search code examples
javascripttypescriptanimationazure-mapsazuremapscontrol

Cannot read properties of undefined in azure-maps-animations


I tried to redo this Azure Maps Animations sample with TypeScript: https://samples.azuremaps.com/animations/animate-marker-along-path

I have several issues and would need some help. I'll explain and provide my code below.

When running in Chrome, the console shows two errors:

Ucaught TypeError: Cannot read properties of undefined (reading 'EventEmitter')
    at azure-maps-animations.js:1101:23
    at azure-maps-animations.js:3424:2

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'moveAlongRoute')
    at main.ts:96:41

I wanted to use VSCode and RequireJs, I used these options in tsconfig.json:

  "compilerOptions": {
    "module": "AMD",
    "target": "ES6",
    "moduleResolution": "node",
    "esModuleInterop": true,

    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "declaration": false,
    "lib": ["ES6", "DOM"],
    "resolveJsonModule": true,
    "downlevelIteration": true,

    "outDir": "./js",
    "rootDir": "./ts",
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "typeRoots": [
      "./node_modules/@types",
      "./types"
    ],
    "baseUrl": "./",
    "paths": {
      "azure-maps-control": ["node_modules/azure-maps-control/dist/atlas.min"]
    },
  }

I used this config.js file to setup RequireJS:

require.config({
  baseUrl: './js',
  paths: {
    'azure-maps-control': 'https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas'
    ,'azure-maps-animations': '../lib/azure-maps/azure-maps-animations'
  }
});

require(['main'], function(main) {
});

My html page only has:

<head>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js" defer></script>
    <script src="./node_modules/requirejs/require.js" defer></script>
    <script src="./config.js" defer></script>
    <link href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas.min.css" rel="stylesheet" />
</head>
<body>
    <div id="myMap"></div>
</body>

Here is the main.ts file:

import * as azmaps from 'azure-maps-control'
import * as atlas from 'azure-maps-animations'

var anim: atlas.RoutePathAnimation;
var map: azmaps.Map;

function main() {

    try {
        map = new azmaps.Map("myMap", {
            center: [-122.345, 47.615],
            zoom: 14,
            view: 'Auto',
            authOptions: {...}
        });

        map.events.add('click', function () {
            //anim.play();
        });

        map.events.add('ready', function () {

            map.imageSprite.createFromTemplate('arrow-icon', 'marker-arrow', 'teal', '#fff').then(function () {

                //Create data sources and add them to the map.
                var lineSource = new azmaps.source.DataSource();
                var pinSource = new azmaps.source.DataSource();
                map.sources.add([lineSource, pinSource]);

                //Create a layer to render the path.
                map.layers.add(new azmaps.layer.LineLayer(lineSource, undefined, {
                    strokeColor: 'DodgerBlue',
                    strokeWidth: 3
                }));

                //Extract the positions to highlight the full route on the map as a line.
                var path: azmaps.data.Position[] = [];

                var routePoints = [
                    new azmaps.data.Feature(new azmaps.data.Point([-122.34758, 47.62155]), { _timestamp: new Date('Tue, 18 Aug 2020 00:53:53 GMT').getTime() }),
                    new azmaps.data.Feature(new azmaps.data.Point([-122.34764, 47.61859]), { _timestamp: new Date('Tue, 18 Aug 2020 00:54:53 GMT').getTime() }),
                    new azmaps.data.Feature(new azmaps.data.Point([-122.33787, 47.61295]), { _timestamp: new Date('Tue, 18 Aug 2020 00:56:53 GMT').getTime() }),
                    new azmaps.data.Feature(new azmaps.data.Point([-122.34217, 47.60964]), { _timestamp: new Date('Tue, 18 Aug 2020 00:59:53 GMT').getTime() })
                ];

                routePoints.forEach(f => {
                    path.push(f.geometry.coordinates);
                });

                lineSource.add(new azmaps.data.LineString(path));

                //Create a layer to render a symbol which we will animate.
                map.layers.add(new azmaps.layer.SymbolLayer(pinSource, undefined, {
                    iconOptions: {
                        //Pass in the id of the custom icon that was loaded into the map resources.
                        image: 'arrow-icon',

                        //Anchor the icon to the center of the image.
                        anchor: 'center',

                        //Rotate the icon based on the rotation property on the point data.
                        //The arrow icon being used in this case points down, so we have to rotate it 180 degrees.
                        rotation: ['+', 180, ['get', 'heading']],

                        //Have the rotation align with the map.
                        rotationAlignment: 'map',

                        //For smoother animation, ignore the placement of the icon. This skips the label collision calculations and allows the icon to overlap map labels. 
                        ignorePlacement: true,

                        //For smoother animation, allow symbol to overlap all other symbols on the map.
                        allowOverlap: true
                    },
                    textOptions: {
                        //For smoother animation, ignore the placement of the text. This skips the label collision calculations and allows the text to overlap map labels.
                        ignorePlacement: true,

                        //For smoother animation, allow text to overlap all other symbols on the map.
                        allowOverlap: true
                    }
                }));

                //Create a pin and wrap with the shape class and add to data source.
                var pin = new azmaps.Shape(routePoints[0]);
                pinSource.add(pin);

                //Create the animation.
                anim = atlas.animations.moveAlongRoute(routePoints, pin, { 
                    //Specify the property that contains the timestamp.
                    //timestampProperty: 'timestamp',

                    captureMetadata: true,
                    loop: false,
                    reverse: false,
                    rotationOffset: 0,
                    
                    speedMultiplier: 60,
                    map: undefined,
                    zoom: 15,
                    pitch: 45,
                    rotate: true
                });
            });
        });
    } catch (error) {
        console.error('Error initializing game:', error);
    }
}

function ready(fn: () => void) {
    if (document.readyState !== 'loading') {
        fn();
    } else {
        document.addEventListener('DOMContentLoaded', fn);
    }
}

ready(() => {
    console.log("addEventListener");
    main();
});

If I uncomment //anim.play(); I get: Property 'play' does not exist on type 'RoutePathAnimation'.

If I uncomment //timestampProperty: 'timestamp', I get: 'timestampProperty' does not exist in type 'RoutePathAnimationOptions'

I noticed that the sample uses atlas namespace for both control and animations imports, I don't know how to do this, could it be the issue ?

The file ../lib/azure-maps/azure-maps-animations.js comes from https://github.com/Azure-Samples/azure-maps-animations/blob/main/dist/azure-maps-animations.js

The file /types/azure-maps-animations.d.ts comes from https://github.com/Azure-Samples/azure-maps-animations/blob/main/typings/index.d.ts

I really love the idea of "timestampProperty" and wish to use it, could you please help me understand the issues ?

Thanks.


Solution

  • The options for the moveAlongRoute don't have a timestampProperty option, so that is a bug and commenting that out (or deleting) is the right thing to do. This is a bug in the sample that doesn't cause any issues in JavaScript as it's simply ignored.

    Note that moveAlongRoute function requires all your points to have a property called _timestamp. If you have timestamp information in another property, pass your points through the extractRoutePoints function first. The has a timestampProperty option.

    This library does require access to the atlas namespace used by Azure Maps.

    In your code you can provide this like follows:

    import * as atlas from "azure-maps-control";