importpathfrom'path';importfsfrom'fs';import{DOMParser}from'xmldom';import{FeatureCollection,LineString}from'geojson';import{gpx}from'@tmcw/togeojson';importgbfrom'geojson-bounds';import*asExifReaderfrom'exifreader';import{Trip,Photo}from'models';import{distanceBetween}from'lib/util';consttripsDirectory=path.join(process.cwd(),'trips');// extract photo metadata from EXIFfunctiongetPhotoMetadata(filePath:string){constbuffer=fs.readFileSync(filePath);consttags=ExifReader.load(buffer,{expanded:true});return{latitude:tags.gps.Latitude,longitude:tags.gps.Longitude,time:tags.exif.DateTime.description.replace(':','-').replace(':','-'),thumbnail:tags.Thumbnail.base64,};}/** * Reads GPX files and photos from trips folder. */exportdefaultfunctiongetTripsData():Trip[]{// get folder names under /tripsconstdirs=fs.readdirSync(tripsDirectory,{withFileTypes:true}).filter((dirent)=>dirent.isDirectory()).map((dirent)=>dirent.name);constallTripsData=dirs.map((dir:string)=>{constdirPath=path.join(tripsDirectory,dir);constfiles=fs.readdirSync(dirPath);constgpxFiles=files.filter((f)=>f.endsWith('.gpx'));// read GPX file as stringconstfullPath=path.join(dirPath,gpxFiles[0]);constfileContents=fs.readFileSync(fullPath,'utf8');constcontentsWithoutNS=fileContents.replace(/\sxmlns[^"]+"[^"]+"/g,'');// create DOM from stringconstdoc=newDOMParser().parseFromString(contentsWithoutNS);// convert GPX to GeoJSONconsttrack:FeatureCollection=gpx(doc);// add bounding boxtrack.bbox=[gb.xMin(track),gb.yMin(track),gb.xMax(track),gb.yMax(track)];const{coordTimes}=track.features[0].properties;// time of the first pointconststart=coordTimes[0];// time of the last pointconstend=coordTimes[coordTimes.length-1];// total duration in secondsconstduration=(newDate(end).getTime()-newDate(start).getTime())/1000;// distance and speedlettotalDistance=0;constspeeds=[];constcoords=(track.features[0].geometryasLineString).coordinates;for(leti=0;i<coords.length-1;i+=1){consta=[coords[i][1],coords[i][0]];constb=[coords[i+1][1],coords[i+1][0]];constdistance=distanceBetween(a,b);if(distance>0){totalDistance+=distance;consttimeBetween=newDate(coordTimes[i+1]).getTime()-newDate(coordTimes[i]).getTime();speeds.push(distance/timeBetween);}}// total distance in kmconstdistance=Math.floor(totalDistance/10)/100;// average speed in km/hconstspeedMps=speeds.reduce((acc,val)=>acc+val)/speeds.length;constspeed=Math.floor(speedMps*3600*100)/100;// photosconstphotoFiles=files.filter((f)=>f.endsWith('.jpg'));constphotos:Photo[]=photoFiles.map((p)=>{// eslint-disable-next-line global-require, import/no-dynamic-require, @typescript-eslint/no-var-requiresconst{src}=require(`trips/${dir}/${p}`);return{name:p,src,...getPhotoMetadata(path.join(dirPath,p)),};});consttrip:Trip={name:dir,track,distance,start,end,duration,speed,photos};returntrip;});// sort trips by namereturnallTripsData.sort((a,b)=>{if(a.name<b.name){return1;}return-1;});}