javascriptnode.jsjsongeojsongpx

Sometimes when the function runs it works perfectly, other times it cannot access the properties of the object


const tj = require('@mapbox/togeojson');
const fs = require('fs');

const gpxToJson = async function (trailSlug) {
  // node doesn't have xml parsing or a dom. use xmldom
  DOMParser = require('xmldom').DOMParser;

  fs.readdir(`./public/traildata/${trailSlug}/gpxFiles/`, (err, files) => {
    let newGpx;
    files.forEach(async (item, i) => {
      const promise = new Promise((res, rej) => {
        fs.readFile(
          `./public/traildata/${trailSlug}/gpxFiles/${item}`,
          'utf8',
          async function (err, data) {
            const gpx = await new DOMParser().parseFromString(data);
            const converted = await tj.gpx(gpx);
            console.log(typeof converted);
            converted.features[0].properties.name = item
              .replace('-', ' ')
              .split('.')[0];

            if (i === 0) {
              newGpx = converted;
            }

            if (i > 0) {
              newGpx.features.push(converted.features[0]);
            }

            if (i === files.length - 1) {
              //   console.log(newGpx);
              fs.writeFile(
                `./public/traildata/${trailSlug}/mastergeoJSON`,
                JSON.stringify(newGpx),
                'utf-8',
                (err) => {
                  if (err) throw err;
                  console.log('The file has been saved');
                }
              );
            }
            res(converted);
          }
        );
      });
      await promise;
    });
  });
};

gpxToJson('terra-cotta');

Hey there, I am trying to make a program that converts GPX files into one GeoJSON File. Everything seems to be going well so far except for every one in 5 times I run the code, I get a TypeError: Cannot read properties of undefined error from "newGpx.features.push(converted.features[0])"

I know the file is an object (as i have done the console.log(typeof converted) and it comes back as an object), I tried promisifying the entire forEach loop (but may have had an issue with the implementation).

Console Screenshot

Does anyone have a resource that might steer me in the right direction?

Thanks in advance.


Solution

  • Here's a much simpler version that uses a for loop which is promise-aware (a .forEach() loop is not promise-aware) and uses the built-in promise versions of readdir(), readFile() and writeFile() which, when combined with await makes the code a lot simpler.

    const tj = require('@mapbox/togeojson');
    const fsp = require('fs').promises;
    const DOMParser = require('xmldom').DOMParser;
    
    const gpxToJson = async function (trailSlug) {
        const srcDir = `./public/traildata/${trailSlug}/gpxFiles/`;
        const files = await fsp.readdir(srcDir);
    
        let newGpx;
        for (let file of files) {
            const fullPath = path.join(srcDir, file);
            const fileData = await fsp.readFile(fullPath, {encoding: 'utf8'});
            const gpx = new DOMParser().parseFromString(fileData);
            const converted = await tj.gpx(gpx);
            converted.features[0].properties.name = file.replace('-', ' ').split('.')[0];
            if (!newGpx) {
                newGpx = converted;
            } else {
                newGpx.features.push(converted.features[0]);
            }
        }
        await fsp.writeFile(`./public/traildata/${trailSlug}/mastergeoJSON`, JSON.stringify(newGPx), { encoding: 'utf8' });
    };
    
    gpxToJson('terra-cotta').catch(err => {
        console.log(err);
    });