RideFile * run() {
        errors.clear();
        rideFile = new RideFile;
        rideFile->setDeviceType("Joule GPS");
        rideFile->setFileFormat("CycleOps Joule (bin2)");
        rideFile->setRecIntSecs(1);

        if (!file.open(QIODevice::ReadOnly)) {
            delete rideFile;
            return NULL;
        }
        bool stop = false;

        int data_size = file.size();
        int bytes_read = 0;

        bytes_read += read_version();
        bytes_read += read_system_info();
        bytes_read += read_summary_page();

        while (!stop && (bytes_read < data_size)) {
            bytes_read += read_detail_page(); // read_page(stop, errors);
        }

        rideFile->setTag("Device Info", deviceInfo);

        if (stop) {
            delete rideFile;
            return NULL;
        }
        else {
            return rideFile;
        }
    }
    RideFile * run() {
        errors.clear();
        rideFile = new RideFile;
        rideFile->setDeviceType("o_synce macro/macrox");
        rideFile->setFileFormat("Macro GoldenCheetah Sync File (osyn)");

        if (!file.open(QIODevice::ReadOnly)) {
            delete rideFile;
            return NULL;
        }
        bool stop = false;

        int data_size = file.size();
        int bytes_read = 0;

        while (!stop && (bytes_read < data_size)) {
            bytes_read += read_page(); // read_page(stop, errors);
        }

        if (stop) {
            file.close();
            delete rideFile;
            return NULL;

        } else {
            file.close();
            return rideFile;
        }
    }
Exemple #3
0
RideFile *RawFileReader::openRideFile(QFile &file, QStringList &errors, QList<RideFile*>*) const
{
    RideFile *rideFile = new RideFile;
    rideFile->setDeviceType("PowerTap");
    rideFile->setFileFormat("GoldenCheetah Raw PowerTap (raw)");
    if (!file.open(QIODevice::ReadOnly)) {
        delete rideFile;
        return NULL;
    }
    FILE *f = fdopen(file.handle(), "r");

    // failed to associate a stream!
    if (f==NULL) {
        file.close();
        delete rideFile;
        return NULL;
    }

    ReadState state(rideFile, errors);
    pt_read_raw(f, &state, config_cb, time_cb, data_cb, error_cb);
    file.close();
    // fclose can handle the file being closed already, QFile crashes on Windows
    // we need to do both because fclose needs to release its stream buffers and
    // fclose maintains local state that causes a crash when out of sync on Windows
    fclose(f);
    return rideFile;
}
    void read_graph_data(double *secs, int *count = NULL, int *sum = NULL)
    {
        double alt = 0, cad = 0, grade = 0, hr = 0, kph = 0, watts = 0;

        double nm = 0, headwind = 0.0, temp = 0.0;
        int lng = 0, lat = 0;

        // Graph data (12 bytes)

        temp = read_bytes(2, count, sum) / 100; // Temperature * 100 (in degree C)

        alt = read_bytes(2, count, sum); //Alti (in m)
        grade = read_bytes(1, count, sum); // Gradient %
        cad = read_bytes(1, count, sum); // Cadence
        kph = read_bytes(2, count, sum)/10.0; // Speed * 10 (in Km/h)
        hr = read_bytes(1, count, sum); // Heart Rate
        watts = read_bytes(2, count, sum); // Power (in Watt)

        int record_data_flag = read_bytes(1, count, sum); // Data Flag

        int  intSecs = 1;
        if ((record_data_flag & 0x03) == 0)
            intSecs = 5;
        else if ((record_data_flag & 0x03) == 1)
            intSecs = 10;
        else if ((record_data_flag & 0x03) == 2)
            intSecs = 20;

        km += intSecs * kph / 3600.0;
        rideFile->setRecIntSecs(intSecs);
        rideFile->appendPoint(*secs, cad, hr, km, kph, nm, watts, alt, lng, lat, headwind, grade, temp, 0.0, interval);

        *secs = *secs + intSecs;

    }
RideFile* readBlocks(const QByteArray& blocks, const qint16 version, QStringList& /*errors*/) {
    RideFile* rideFile = new RideFile();

    rideFile->setDeviceType(TACX_FORTIUS_DEVICE_TYPE);
    rideFile->setFileFormat("Tacx Fortius (caf)");


    QByteArray remainingBytes = blocks;
    while(!remainingBytes.isEmpty()) {
        const QByteArray blockHeaderBytes = remainingBytes.left(12);
        header_t blockHeader = readBlockHeader(blockHeaderBytes);

        switch(blockHeader.fingerprint) {
        case TACX_RIDE_INFORMATION_BLOCK:
            readRideInformationBlock(rideFile, remainingBytes.mid(12));
            break;
        case TACX_RIDE_DATA_BLOCK:
            readRideData(rideFile, remainingBytes.mid(12), blockHeader.recordCount, version);
            break;
        }

        int skipBytes = 12 + blockHeader.recordCount * blockHeader.recordSize;

        remainingBytes = remainingBytes.mid(skipBytes);
    }

    return rideFile;
}
RideFile *RawFileReader::openRideFile(QFile &file, QStringList &errors) const
{
    RideFile *rideFile = new RideFile;
    rideFile->setDeviceType("PowerTap");
    if (!file.open(QIODevice::ReadOnly)) {
        delete rideFile;
        return NULL;
    }
    FILE *f = fdopen(file.handle(), "r");
    assert(f);
    ReadState state(rideFile, errors);
    pt_read_raw(f, &state, config_cb, time_cb, data_cb, error_cb);
    return rideFile;
}
RideFile *SmfFileReader::openRideFile(QFile &file, QStringList &errors, QList<RideFile*>*) const
{
    (void) errors;
    RideFile *rideFile = new RideFile();
    rideFile->setDeviceType("Sigma ROX Memory");

    SmfParser handler(rideFile);

    QXmlInputSource source (&file);
    QXmlSimpleReader reader;
    reader.setContentHandler (&handler);
    reader.parse (source);

    return rideFile;
}
    RideFile * run() {
        errors.clear();
        rideFile = new RideFile;
        rideFile->setDeviceType("Macro Sync");

        if (!file.open(QIODevice::ReadOnly)) {
            delete rideFile;
            return NULL;
        }
        bool stop = false;

        int data_size = file.size();
        int bytes_read = 0;

        while (!stop && (bytes_read < data_size)) {
            bytes_read += read_page(); // read_page(stop, errors);
        }

        if (stop) {
            delete rideFile;
            return NULL;
        }
        else {
            return rideFile;
        }
    }
RideFile *TcxFileReader::openRideFile(QFile &file, QStringList &errors, QList<RideFile*>*list) const
{
    (void) errors;
    RideFile *rideFile = new RideFile();
    rideFile->setRecIntSecs(1.0);
    rideFile->setDeviceType("Garmin TCX");

    TcxParser handler(rideFile, list);

    QXmlInputSource source (&file);
    QXmlSimpleReader reader;
    reader.setContentHandler (&handler);
    reader.parse (source);

    return rideFile;
}
RideFile *QuarqFileReader::openRideFile(QFile &file, QStringList &errors, QList<RideFile*>*) const
{
    (void) errors;
    RideFile *rideFile = new RideFile();
    rideFile->setDeviceType("Quarq Qollector");
    rideFile->setFileFormat("Quarq ANT+ Files (qla)");
    rideFile->setRecIntSecs(1.0);

    QuarqParser handler(rideFile);

    QProcess *antProcess = getInterpreterProcess( installed_path );

    assert(antProcess);

    QXmlInputSource source (antProcess);
    QXmlSimpleReader reader;
    reader.setContentHandler (&handler);

    // this could done be a loop to "save memory."
    file.open(QIODevice::ReadOnly);
    antProcess->write(file.readAll());
    antProcess->closeWriteChannel();
    antProcess->waitForFinished(-1);

    assert(QProcess::NormalExit == antProcess->exitStatus());
    assert(0 == antProcess->exitCode());

    reader.parse(source);

    reader.parseContinue();

    QRegExp rideTime("^.*/(\\d\\d\\d\\d)_(\\d\\d)_(\\d\\d)_"
                     "(\\d\\d)_(\\d\\d)_(\\d\\d)\\.qla$");
    if (rideTime.indexIn(file.fileName()) >= 0) {
        QDateTime datetime(QDate(rideTime.cap(1).toInt(),
                                 rideTime.cap(2).toInt(),
                                 rideTime.cap(3).toInt()),
                           QTime(rideTime.cap(4).toInt(),
                                 rideTime.cap(5).toInt(),
                                 rideTime.cap(6).toInt()));
        rideFile->setStartTime(datetime);
    }

    delete antProcess;

    return rideFile;
}
    void read_detail_record(double *secs, int *bytes_read = NULL, int *sum = NULL)
    {
        int cad = read_bytes(1, bytes_read, sum);
        read_bytes(1, bytes_read, sum); // pedal_smoothness
        int lrbal = read_bytes(1, bytes_read, sum);
        int hr = read_bytes(1, bytes_read, sum);
        read_bytes(1, bytes_read, sum); // dummy
        int watts = read_bytes(2, bytes_read, sum);
        int nm = read_bytes(2, bytes_read, sum);
        double kph = read_bytes(2, bytes_read, sum);
        int alt = read_bytes(2, bytes_read, sum);
        double temp = read_bytes(2, bytes_read, sum)/10.0;
        double lat = read_bytes(4, bytes_read, sum);
        double lng = read_bytes(4, bytes_read, sum);
        double km = read_bytes(8, bytes_read, sum)/1000.0/1000.0;

        // Validations
        if (lrbal == 0xFF)
            lrbal = 0;
        else if ((lrbal & 0x200) == 0x200)
            lrbal = 100-lrbal;

        if (cad == 0xFF)
            cad = 0;

        if (hr == 0xFF)
            hr = 0;

        if (watts == 0xFFFF) // 65535
            watts = 0;

        if (kph == 0xFFFF) // 65535
            kph = 0;
        else
            kph = kph/10.0;

        if (temp == 0x8000)
            temp = 0;

        if (alt == 0x8000)
            alt = 0;

        if (lat == -2147483648) //2147483648
            lat = 0;
        else
            lat = lat/10000000.0;

        if (lng == -2147483648) //0x80000000
            lng = 0;
        else
            lng = lng/10000000.0;

        rideFile->appendPoint(*secs, cad, hr, km, kph, nm, watts, alt, lng, lat, 0.0, 0, temp, lrbal, interval);
        (*secs)++;
    }
    void read_ride_summary(int *bytes_read = NULL, int *sum = NULL)
    {
        read_bytes(1, bytes_read, sum); // data_version
        read_bytes(1, bytes_read, sum); // firmware_minor_version

        QDateTime t = read_date(bytes_read, sum);

        rideFile->setStartTime(t);

        read_bytes(148, bytes_read, sum);
    }
Exemple #13
0
void
Athlete::updateRideFileIntervals()
{
    // iterate over context->athlete->allIntervals as they are now defined
    // and update the RideFile->intervals
    RideItem *which = (RideItem *)treeWidget->selectedItems().first();
    RideFile *current = which->ride();
    current->clearIntervals();
    for (int i=0; i < allIntervals->childCount(); i++) {
        // add the intervals as updated
        IntervalItem *it = (IntervalItem *)allIntervals->child(i);
        current->addInterval(it->start, it->stop, it->text(0));
    }

    // emit signal for interval data changed
    context->notifyIntervalsChanged();

    // set dirty
    which->setDirty(true);
}
void
HrPwPlot::setDataFromRide(RideItem *_rideItem)
{
    rideItem = _rideItem;

    // ignore null / bad rides
    if (!_rideItem || !_rideItem->ride()) return;

    RideFile *ride = rideItem->ride();

    const RideFileDataPresent *dataPresent = ride->areDataPresent();
    int npoints = ride->dataPoints().size();

    if (dataPresent->watts && dataPresent->hr) {
        wattsArray.resize(npoints);
        hrArray.resize(npoints);
        timeArray.resize(npoints);
        interArray.resize(npoints);

        arrayLength = 0;
        //QListIterator<RideFilePoint*> i(ride->dataPoints());
        //while (i.hasNext()) {
        foreach (const RideFilePoint *point, ride->dataPoints()) {
            //RideFilePoint *point = i.next();
            if (!timeArray.empty())
                timeArray[arrayLength]  = point->secs;
            if (!wattsArray.empty())
                wattsArray[arrayLength] = max(0, point->watts);
            if (!hrArray.empty())
                hrArray[arrayLength]    = max(0, point->hr);
            if (!interArray.empty())
                interArray[arrayLength] = point->interval;
            ++arrayLength;
        }

        delay = -1;
        recalc();
    }
Exemple #15
0
void
Tab::rideSelected(RideItem*)
{
    // update the ride property on all widgets
    // to let them know they need to replot new
    // selected ride
    setRide(context->ride);

    if (!context->ride) return;

    // refresh interval list for bottom left
    // first lets wipe away the existing intervals
    QList<QTreeWidgetItem *> intervals = context->athlete->allIntervals->takeChildren();
    for (int i=0; i<intervals.count(); i++) delete intervals.at(i);

    // now add the intervals for the current ride
    if (context->ride) { // only if we have a ride pointer
        RideFile *selected = context->ride->ride();
        if (selected) {
            // get all the intervals in the currently selected RideFile
            QList<RideFileInterval> intervals = selected->intervals();
            for (int i=0; i < intervals.count(); i++) {
                // add as a child to context->athlete->allIntervals
                IntervalItem *add = new IntervalItem(selected,
                                                        intervals.at(i).name,
                                                        intervals.at(i).start,
                                                        intervals.at(i).stop,
                                                        selected->timeToDistance(intervals.at(i).start),
                                                        selected->timeToDistance(intervals.at(i).stop),
                                                        context->athlete->allIntervals->childCount()+1);
                add->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
                context->athlete->allIntervals->addChild(add);
            }
        }
    }
}
    int read_version()
    {
        int sum = 0;
        int bytes_read = 0;

        uint16_t header = read_bytes(2, &bytes_read, &sum); // Always 0x210 (0x10-0x02)
        uint16_t command = read_bytes(2, &bytes_read, &sum);

        if (header == START && command == UNIT_VERSION)
        {
            read_bytes(2, &bytes_read, &sum); // length

            int major_version = read_bytes(1, &bytes_read, &sum);
            int minor_version = read_bytes(2, &bytes_read, &sum);
            int data_version = read_bytes(2, &bytes_read, &sum);

            QString version = QString(minor_version<100?"%1.0%2 (%3)":"%1.%2 (%3)").arg(major_version).arg(minor_version).arg(data_version);
            deviceInfo += rideFile->deviceType()+QString(" Version %1\n").arg(version);

            read_bytes(1, &bytes_read, &sum); // checksum
        }
        return bytes_read;
    }
    void read_ride_summary(int *bytes_read = NULL, int *sum = NULL)
    {
        data_version = read_bytes(1, bytes_read, sum); // data_version
        read_bytes(1, bytes_read, sum); // firmware_minor_version

        QDateTime t = read_date(bytes_read, sum);

        rideFile->setStartTime(t);

        if (jouleGPS)
        {
            read_bytes(148, bytes_read, sum);

            if (data_version >= 4)
                read_bytes(8, bytes_read, sum);

            if (data_version >= 6)
                read_bytes(8, bytes_read, sum);
       } else
       {
            read_bytes(84, bytes_read, sum);
       }
    }
RideFile *SrmFileReader::openRideFile(QFile &file, QStringList &errorStrings) const
{
    if (!file.open(QFile::ReadOnly)) {
        errorStrings << QString("can't open file %1").arg(file.fileName());
        return NULL;
    }
    QDataStream in(&file);
    RideFile *result = new RideFile;
    result->setDeviceType("SRM");

    char magic[4];
    in.readRawData(magic, sizeof(magic));
    assert(strncmp(magic, "SRM", 3) == 0);
    int version = magic[3] - '0';
    assert(version == 5 || version == 6 || version == 7);

    quint16 dayssince1880 = readShort(in);
    quint16 wheelcirc = readShort(in);
    quint8 recint1 = readByte(in);
    quint8 recint2 = readByte(in);
    quint16 blockcnt = readShort(in);
    quint16 markercnt = readShort(in);
    readByte(in); // padding
    quint8 commentlen = readByte(in);

    char comment[71];
    in.readRawData(comment, sizeof(comment) - 1);
    comment[commentlen - 1] = '\0';

    result->setRecIntSecs(((double) recint1) / recint2);
    unsigned recintms = (unsigned) round(result->recIntSecs() * 1000.0);

    QDate date(1880, 1, 1);
    date = date.addDays(dayssince1880);

    QVector<marker> markers(markercnt + 1);
    for (int i = 0; i <= markercnt; ++i) {
        char mcomment[256];
        size_t mcommentlen = version < 6 ? 3 : 255;
        assert( mcommentlen < sizeof(mcomment) );
        in.readRawData(mcomment, mcommentlen );
        mcomment[mcommentlen] = '\0';

        quint8 active = readByte(in);
        quint16 start = readShort(in);
        quint16 end = readShort(in);
        quint16 avgwatts = readShort(in);
        quint16 avghr = readShort(in);
        quint16 avgcad = readShort(in);
        quint16 avgspeed = readShort(in);
        quint16 pwc150 = readShort(in);

	// data fixup: Although the data chunk index in srm files starts
	// with 1, some srmwin wrote files referencing index 0.
	if( end < 1 ) end = 1;
	if( start < 1 ) start = 1;

	// data fixup: some srmwin versions wrote markers with start > end
	if( end < start ){
		markers[i].start = end;
		markers[i].end = start;
	} else {
		markers[i].start = start;
		markers[i].end = end;
	}

        (void) active;
        (void) avgwatts;
        (void) avghr;
        (void) avgcad;
        (void) avgspeed;
        (void) pwc150;
        (void) wheelcirc;
    }

    blockhdr *blockhdrs = new blockhdr[blockcnt+1];
    for (int i = 0; i < blockcnt; ++i) {
        // In the .srm files generated by Rainer Clasen's srmcmd,
        // hsecsincemidn is a *signed* 32-bit integer.  I haven't seen a
        // negative value in any .srm files generated by srmwin.exe, but
        // since the number of hundredths of a second in a day is << 2^31,
        // it seems safe to always treat this number as signed.
        qint32 hsecsincemidn = readLong(in);
        blockhdrs[i].chunkcnt = readShort(in);
        blockhdrs[i].dt = QDateTime(date);
        blockhdrs[i].dt = blockhdrs[i].dt.addMSecs(hsecsincemidn * 10);
    }

    quint16 zero = readShort(in);
    quint16 slope = readShort(in);
    quint16 datacnt = readShort(in);
    readByte(in); // padding

    (void) zero;
    (void) slope;

    // SRM5 files have no blocks - synthesize one
    if( blockcnt < 1 ){
        blockcnt = 0;
        blockhdrs[0].chunkcnt = datacnt;
        blockhdrs[0].dt = QDateTime(date);
    }


    int blknum = 0, blkidx = 0, mrknum = 0, interval = 0;
    double km = 0.0, secs = 0.0;

    if (markercnt > 0)
        mrknum = 1;

    for (int i = 0; i < datacnt; ++i) {
        int cad, hr, watts;
        double kph, alt;
        if (version < 7) {
            quint8 ps[3];
            in.readRawData((char*) ps, sizeof(ps));
            cad = readByte(in);
            hr = readByte(in);
            kph = (((((unsigned) ps[1]) & 0xf0) << 3)
                   | (ps[0] & 0x7f)) * 3.0 / 26.0;
            watts = (ps[1] & 0x0f) | (ps[2] << 0x4);
            alt = 0.0;
        }
        else {
            assert(version == 7);
            watts = readShort(in);
            cad = readByte(in);
            hr = readByte(in);
            kph = readLong(in) * 3.6 / 1000.0;
            alt = readLong(in);
            double temp = 0.1 * (qint16) readShort(in);
            (void) temp; // unused for now
        }

        if (i == 0) {
            result->setStartTime(blockhdrs[blknum].dt);
        }
        if (mrknum < markers.size() && i == markers[mrknum].end) {
            ++interval;
            ++mrknum;
        }

        // markers count from 1
        if ((i > 0) && (mrknum < markers.size()) && (i == markers[mrknum].start - 1))
            ++interval;

        km += result->recIntSecs() * kph / 3600.0;

        double nm = watts / 2.0 / PI / cad * 60.0;
        result->appendPoint(secs, cad, hr, km, kph, nm, watts, alt, 0.0, 0.0, 0.0, interval);

        ++blkidx;
        if ((blkidx == blockhdrs[blknum].chunkcnt) && (blknum + 1 < blockcnt)) {
            QDateTime end = blockhdrs[blknum].dt.addMSecs(
                recintms * blockhdrs[blknum].chunkcnt);
            ++blknum;
            blkidx = 0;
            QDateTime start = blockhdrs[blknum].dt;
            qint64 endms =
                ((qint64) end.toTime_t()) * 1000 + end.time().msec();
            qint64 startms =
                ((qint64) start.toTime_t()) * 1000 + start.time().msec();
            double diff_secs = (startms - endms) / 1000.0;
            if (diff_secs < result->recIntSecs()) {
                errorStrings << QString("ERROR: time goes backwards by %1 s"
                                        " on trans " "to block %2"
                                        ).arg(diff_secs).arg(blknum);
                secs += result->recIntSecs(); // for lack of a better option
            }
            else {
                secs += diff_secs;
            }
        }
        else {
            secs += result->recIntSecs();
        }
    }

    double last = 0.0;
    for (int i = 1; i < markers.size(); ++i) {
        const marker &marker = markers[i];
        int start = marker.start - 1;
        double start_secs = result->dataPoints()[start]->secs;
        int end = qMin(marker.end - 1, result->dataPoints().size() - 1);
        double end_secs = result->dataPoints()[end]->secs + result->recIntSecs();
        result->addInterval(last, start_secs, "");
        result->addInterval(start_secs, end_secs, QString("%1").arg(i));
        last = end_secs;
    }
    if (!markers.empty() && markers.last().end < result->dataPoints().size()) {
        double start_secs = result->dataPoints().last()->secs + result->recIntSecs();
        result->addInterval(last, start_secs, "");
    }

    file.close();
    return result;
}
Exemple #19
0
void
PowerHist::setData(RideItem *_rideItem)
{
    rideItem = _rideItem;

    RideFile *ride = rideItem->ride();

    if (ride) {
        setTitle(ride->startTime().toString(GC_DATETIME_FORMAT));

        static const int maxSize = 4096;

        // recording interval in minutes
        dt = ride->recIntSecs() / 60.0;

        wattsArray.resize(0);
        wattsZoneArray.resize(0);
        nmArray.resize(0);
        hrArray.resize(0);
        hrZoneArray.resize(0);
        kphArray.resize(0);
        cadArray.resize(0);

        wattsSelectedArray.resize(0);
        wattsZoneSelectedArray.resize(0);
        nmSelectedArray.resize(0);
        hrSelectedArray.resize(0);
        hrZoneSelectedArray.resize(0);
        kphSelectedArray.resize(0);
        cadSelectedArray.resize(0);

        // unit conversion factor for imperial units for selected parameters
        double torque_factor = (useMetricUnits ? 1.0 : 0.73756215);
        double speed_factor  = (useMetricUnits ? 1.0 : 0.62137119);

        foreach(const RideFilePoint *p1, ride->dataPoints()) {
            bool selected = isSelected(p1, ride->recIntSecs());

            // watts array
            int wattsIndex = int(floor(p1->watts / wattsDelta));
            if (wattsIndex >= 0 && wattsIndex < maxSize) {
                if (wattsIndex >= wattsArray.size())
                    wattsArray.resize(wattsIndex + 1);
                wattsArray[wattsIndex]++;

                if (selected) {
                    if (wattsIndex >= wattsSelectedArray.size())
                        wattsSelectedArray.resize(wattsIndex + 1);
                    wattsSelectedArray[wattsIndex]++;
                }
            }

            // watts zoned array
            const Zones *zones = rideItem->zones;
            int zoneRange = zones ? zones->whichRange(ride->startTime().date()) : -1;

            // Only calculate zones if we have a valid range and check zeroes
            if (zoneRange > -1 && (withz || (!withz && p1->watts))) {
                wattsIndex = zones->whichZone(zoneRange, p1->watts);

                if (wattsIndex >= 0 && wattsIndex < maxSize) {
                    if (wattsIndex >= wattsZoneArray.size())
                        wattsZoneArray.resize(wattsIndex + 1);
                    wattsZoneArray[wattsIndex]++;

                    if (selected) {
                        if (wattsIndex >= wattsZoneSelectedArray.size())
                            wattsZoneSelectedArray.resize(wattsIndex + 1);
                        wattsZoneSelectedArray[wattsIndex]++;
                    }
                }
            }

            int nmIndex = int(floor(p1->nm * torque_factor / nmDelta));
            if (nmIndex >= 0 && nmIndex < maxSize) {
                if (nmIndex >= nmArray.size())
                    nmArray.resize(nmIndex + 1);
                nmArray[nmIndex]++;

                if (selected) {
                    if (nmIndex >= nmSelectedArray.size())
                        nmSelectedArray.resize(nmIndex + 1);
                    nmSelectedArray[nmIndex]++;
                }
            }

	    int hrIndex = int(floor(p1->hr / hrDelta));
	    if (hrIndex >= 0 && hrIndex < maxSize) {
            if (hrIndex >= hrArray.size())
               hrArray.resize(hrIndex + 1);
            hrArray[hrIndex]++;

            if (selected) {
                if (hrIndex >= hrSelectedArray.size())
                    hrSelectedArray.resize(hrIndex + 1);
                hrSelectedArray[hrIndex]++;
            }
	    }

        // hr zoned array
        int hrZoneRange = mainWindow->hrZones() ? mainWindow->hrZones()->whichRange(ride->startTime().date()) : -1;

        // Only calculate zones if we have a valid range
        if (hrZoneRange > -1 && (withz || (!withz && p1-hr))) {
            hrIndex = mainWindow->hrZones()->whichZone(hrZoneRange, p1->hr);

            if (hrIndex >= 0 && hrIndex < maxSize) {
                if (hrIndex >= hrZoneArray.size())
                    hrZoneArray.resize(hrIndex + 1);
                hrZoneArray[hrIndex]++;

                if (selected) {
                    if (hrIndex >= hrZoneSelectedArray.size())
                        hrZoneSelectedArray.resize(hrIndex + 1);
                    hrZoneSelectedArray[hrIndex]++;
                }
            }
        }

	    int kphIndex = int(floor(p1->kph * speed_factor / kphDelta));
	    if (kphIndex >= 0 && kphIndex < maxSize) {
	        if (kphIndex >= kphArray.size())
	            kphArray.resize(kphIndex + 1);
	        kphArray[kphIndex]++;

            if (selected) {
                if (kphIndex >= kphSelectedArray.size())
                    kphSelectedArray.resize(kphIndex + 1);
                kphSelectedArray[kphIndex]++;
            }
	    }

	    int cadIndex = int(floor(p1->cad / cadDelta));
	    if (cadIndex >= 0 && cadIndex < maxSize) {
		    if (cadIndex >= cadArray.size())
		        cadArray.resize(cadIndex + 1);
		    cadArray[cadIndex]++;

            if (selected) {
                if (cadIndex >= cadSelectedArray.size())
                    cadSelectedArray.resize(cadIndex + 1);
                cadSelectedArray[cadIndex]++;
            }
	    }


	}

	recalc();
    }
    else {
RideFile *ManualFileReader::openRideFile(QFile &file, QStringList &errors, QList<RideFile*>*) const
{
    QRegExp metricUnits("(km|kph|km/h)", Qt::CaseInsensitive);
    QRegExp englishUnits("(miles|mph|mp/h)", Qt::CaseInsensitive);
    bool metric = false;

    int unitsHeader = 2;

    /*
     *  File format:
     * 	"manual"
     * 	"minutes,mph,watts,miles,hr,bikescore"  # header (metric or imperial)
     *	minutes,mph,watts,miles,hr,bikeScore    # data
     */
    QRegExp manualCSV("manual", Qt::CaseInsensitive);

    double rideSec = 0;

    if (!file.open(QFile::ReadOnly)) {
	errors << (tr("Could not open ride file: \"")
		+ file.fileName() + "\"");
	return NULL;
    }
    int lineno = 1;
    QStringList columnNames;
    QTextStream is(&file);
    RideFile *rideFile = new RideFile();
    while (!is.atEnd()) {
	// the readLine() method doesn't handle old Macintosh CR line endings
	// this workaround will load the entire file if it has CR endings
	// then split and loop through each line
	// otherwise, there will be nothing to split and it will read each line as expected.
	QString linesIn = is.readLine();
	QStringList lines = linesIn.split('\r');
	// workaround for empty lines
	if(lines.isEmpty()) {
	    lineno++;
	    continue;
	}
	for (int li = 0; li < lines.size(); ++li) {
	    QString line = lines[li];

	    if (lineno == 1) {
		if (manualCSV.indexIn(line) != -1) {
            rideFile->setDeviceType("Manual");
            rideFile->setFileFormat("Manual CSV (csv)");
		    ++lineno;
		    continue;
		}
	    }
	    else if (lineno == unitsHeader) {
		if (metricUnits.indexIn(line) != -1)
		    metric = true;
		else if (englishUnits.indexIn(line) != -1)
		    metric = false;
		else {
		    errors << (tr("Can't find units in first line: \"") + line + "\"");
		    delete rideFile;
		    file.close();
		    return NULL;
		}
                columnNames = line.split(",");
		++lineno;
		continue;
	    }
	    // minutes,kph,watts,km,hr,bikeScore
	    else if (lineno > unitsHeader) {
		double minutes=0,kph=0,watts=0,km=0,hr=0,alt=0,bs=0;
		double cad=0, nm=0;
		int interval=0;
                QStringList fields = line.split(",");
                minutes = fields[0].toDouble();
                kph = fields[1].toDouble();
                watts = fields[2].toDouble();
                km = fields[3].toDouble();
                hr = fields[4].toDouble();
                bs = fields[5].toDouble();
		if (!metric) {
		    km *= KM_PER_MILE;
		    kph *= KM_PER_MILE;
		}
                const RideMetricFactory &factory = RideMetricFactory::instance();
                for (int i = 6; i < fields.size(); ++i) {
                    if (factory.haveMetric(columnNames[i])) {
                        QMap<QString,QString> map;
                        map.insert("value", QString("%1").arg(fields[i]));
                        rideFile->metricOverrides.insert(columnNames[i], map);
                    }
                    else {
                        errors << tr("Unknown ride metric \"%1\".").arg(columnNames[i]);
                    }
                }
		cad = nm = 0.0;
		interval = 0;

		rideFile->appendPoint(minutes * 60.0, cad, hr, km,
                                      kph, nm, watts, alt,
                                      0.0, 0.0, 0.0, 0.0,
                                      RideFile::NA, RideFile::NA,
                                      0.0, 0.0, 0.0, 0.0,
                                      0.0, 0.0, // pedal platform offset
                                      0.0, 0.0, 0.0, 0.0, //pedal power phase
                                      0.0, 0.0, 0.0, 0.0, //pedal peak power phase
                                      0.0, 0.0,
                                      0.0, 0.0, 0.0, // running dynamics
                                      0.0, //tcore
                                      interval);
                QMap<QString,QString> bsm;
                bsm.insert("value", QString("%1").arg(bs));
                rideFile->metricOverrides.insert("skiba_bike_score", bsm);
                QMap<QString,QString> trm;
                trm.insert("value", QString("%1").arg(minutes * 60.0));
                rideFile->metricOverrides.insert("time_riding", trm);

		rideSec = minutes * 60.0;
	    }
	    ++lineno;
	}
    }
    // fix recording interval at ride length:
    rideFile->setRecIntSecs(rideSec);

    QRegExp rideTime("^.*/(\\d\\d\\d\\d)_(\\d\\d)_(\\d\\d)_"
	    "(\\d\\d)_(\\d\\d)_(\\d\\d)\\.man$");
    if (rideTime.indexIn(file.fileName()) >= 0) {
	QDateTime datetime(QDate(rideTime.cap(1).toInt(),
		    rideTime.cap(2).toInt(),
		    rideTime.cap(3).toInt()),
		QTime(rideTime.cap(4).toInt(),
		    rideTime.cap(5).toInt(),
		    rideTime.cap(6).toInt()));
	rideFile->setStartTime(datetime);
    }
    file.close();
    return rideFile;
}
RideFile *SrmFileReader::openRideFile(QFile &file, QStringList &errorStrings, QList<RideFile*>*) const
{
    if (!file.open(QFile::ReadOnly)) {
        errorStrings << QString("can't open file %1").arg(file.fileName());
        return NULL;
    }
    QDataStream in(&file);
    in.setByteOrder( QDataStream::LittleEndian );

    RideFile *result = new RideFile;
    result->setDeviceType("SRM");
    result->setFileFormat("SRM training files (srm)");
    result->setTag("Sport", "Bike" );

    char magic[4];
    in.readRawData(magic, sizeof(magic));
    if( strncmp(magic, "SRM", 3)){
        errorStrings << QString("Unrecognized file type, missing magic." );
        return NULL;
    }

    int version = magic[3] - '0';
    switch( version ){
      case 5:
      case 6:
      case 7:
        // ok
        break;

      default:
        errorStrings << QString("Unsupported SRM file format version: %1")
            .arg(version);
        return NULL;
    }

    quint16 dayssince1880 = readShort(in);
    quint16 wheelcirc = readShort(in);
    quint8 recint1 = readByte(in);
    quint8 recint2 = readByte(in);
    quint16 blockcnt = readShort(in);
    quint16 markercnt = readShort(in);
    readByte(in); // padding
    quint8 commentlen = readByte(in);

    if( commentlen > 70 )
        commentlen = 70;

    char comment[71];
    in.readRawData(comment, sizeof(comment) - 1);
    comment[commentlen] = '\0';
    result->setTag("Notes", QString(comment) );

    // assert propper markercnt to avoid segfaults
    if( in.status() != QDataStream::Ok ){
        errorStrings << QString("failed to read file header" );
        return NULL;
    }

    result->setRecIntSecs(((double) recint1) / recint2);
    unsigned recintms = (unsigned) round(result->recIntSecs() * 1000.0);

    result->setTag("Wheel Circumference", QString("%1").arg(wheelcirc) );

    QDate date(1880, 1, 1);
    date = date.addDays(dayssince1880);

    QVector<marker> markers(markercnt + 1);
    for (int i = 0; i <= markercnt; ++i) {
        char mcomment[256];
        size_t mcommentlen = version < 6 ? 3 : 255;
        assert( mcommentlen < sizeof(mcomment) );
        in.readRawData(mcomment, mcommentlen );
        mcomment[mcommentlen] = '\0';

        quint8 active = readByte(in);
        quint16 start = readShort(in);
        quint16 end = readShort(in);
        quint16 avgwatts = readShort(in);
        quint16 avghr = readShort(in);
        quint16 avgcad = readShort(in);
        quint16 avgspeed = readShort(in);
        quint16 pwc150 = readShort(in);

    // data fixup: Although the data chunk index in srm files starts
    // with 1, some srmwin wrote files referencing index 0.
    if( end < 1 ) end = 1;
    if( start < 1 ) start = 1;

    // data fixup: some srmwin versions wrote markers with start > end
    if( end < start ){
        markers[i].start = end;
        markers[i].end = start;
    } else {
        markers[i].start = start;
        markers[i].end = end;
    }

        markers[i].note = QString( mcomment);

        if( i == 0 ){
            result->setTag("Athlete Name", QString(mcomment) );
        }

        (void) active;
        (void) avgwatts;
        (void) avghr;
        (void) avgcad;
        (void) avgspeed;
        (void) pwc150;
        (void) wheelcirc;
    }

    // fail early to tell devs whats wrong with file
    if( in.status() != QDataStream::Ok ){
        errorStrings << QString("failed to read marker" );
        return NULL;
    }

    blockhdr *blockhdrs = new blockhdr[blockcnt+1];
    for (int i = 0; i < blockcnt; ++i) {
        // In the .srm files generated by Rainer Clasen's srmcmd,
        // hsecsincemidn is a *signed* 32-bit integer.  I haven't seen a
        // negative value in any .srm files generated by srmwin.exe, but
        // since the number of hundredths of a second in a day is << 2^31,
        // it seems safe to always treat this number as signed.
        qint32 hsecsincemidn = readLong(in);
        blockhdrs[i].chunkcnt = readShort(in);
        blockhdrs[i].dt = QDateTime(date);
        blockhdrs[i].dt = blockhdrs[i].dt.addMSecs(hsecsincemidn * 10);
    }

    // fail early to tell devs whats wrong with file
    if( in.status() != QDataStream::Ok ){
        errorStrings << QString("failed to read block headers" );
        return NULL;
    }

    quint16 zero = readShort(in);
    quint16 slope = readShort(in);
    quint16 datacnt = readShort(in);
    readByte(in); // padding

    // fail early to tell devs whats wrong with file
    if( in.status() != QDataStream::Ok ){
        errorStrings << QString("failed to read calibration data" );
        return NULL;
    }

    result->setTag("Slope", QString("%1")
        .arg( 140.0 / 42781 * slope, 0, 'f', 2) );
    result->setTag("Zero Offset", QString("%1").arg(zero) );

    // SRM5 files have no blocks - synthesize one
    if( blockcnt < 1 ){
        blockcnt = 0;
        blockhdrs[0].chunkcnt = datacnt;
        blockhdrs[0].dt = QDateTime(date);
    }


    int blknum = 0, blkidx = 0, mrknum = 0, interval = 0;
    double km = 0.0, secs = 0.0;

    if (markercnt > 0)
        mrknum = 1;

    for (int i = 0; i < datacnt; ++i) {
        int cad, hr, watts;
        double kph, alt;
        double temp=-255;
        if (version < 7) {
            quint8 ps[3];
            in.readRawData((char*) ps, sizeof(ps));
            cad = readByte(in);
            hr = readByte(in);
            kph = (((((unsigned) ps[1]) & 0xf0) << 3)
                   | (ps[0] & 0x7f)) * 3.0 / 26.0;
            watts = (ps[1] & 0x0f) | (ps[2] << 0x4);
            alt = 0.0;
        }
        else {
            assert(version == 7);
            watts = readShort(in);
            cad = readByte(in);
            hr = readByte(in);

            qint32 kph_tmp = readSignedLong(in);
            kph = kph_tmp < 0 ? 0 : kph_tmp * 3.6 / 1000.0;

            alt = readSignedLong(in);
            temp = 0.1 * readSignedShort(in);
        }

        if (i == 0) {
            result->setStartTime(blockhdrs[blknum].dt);
        }
        if (mrknum < markers.size() && i == markers[mrknum].end) {
            ++interval;
            ++mrknum;
        }

        // markers count from 1
        if ((i > 0) && (mrknum < markers.size()) && (i == markers[mrknum].start - 1))
            ++interval;

        km += result->recIntSecs() * kph / 3600.0;

        double nm = watts / 2.0 / PI / cad * 60.0;
        result->appendPoint(secs, cad, hr, km, kph, nm, watts, alt, 0.0, 0.0, 0.0, 0.0, temp, 0.0, interval);

        ++blkidx;
        if ((blkidx == blockhdrs[blknum].chunkcnt) && (blknum + 1 < blockcnt)) {
            QDateTime end = blockhdrs[blknum].dt.addMSecs(
                recintms * blockhdrs[blknum].chunkcnt);
            ++blknum;
            blkidx = 0;
            QDateTime start = blockhdrs[blknum].dt;
            qint64 endms =
                ((qint64) end.toTime_t()) * 1000 + end.time().msec();
            qint64 startms =
                ((qint64) start.toTime_t()) * 1000 + start.time().msec();
            double diff_secs = (startms - endms) / 1000.0;
            if (diff_secs < result->recIntSecs()) {
                errorStrings << QString("ERROR: time goes backwards by %1 s"
                                        " on trans " "to block %2"
                                        ).arg(diff_secs).arg(blknum);
                secs += result->recIntSecs(); // for lack of a better option
            }
            else {
                secs += diff_secs;
            }
        }
        else {
            secs += result->recIntSecs();
        }
    }

    // assert some points were found. prevents segfault when looking at
    // the overall markers[0].start/.end
    // note: we're not checking in.status() to cope with truncated files

    if( result->dataPoints().size() < 1 ){
        errorStrings << QString("file contains no data points");
        return NULL;
    }

    double last = 0.0;
    for (int i = 1; i < markers.size(); ++i) {
        const marker &marker = markers[i];
        int start = qMin(marker.start, result->dataPoints().size()) - 1;
        double start_secs = result->dataPoints()[start]->secs;
        int end = qMin(marker.end - 1, result->dataPoints().size() - 1);
        double end_secs = result->dataPoints()[end]->secs + result->recIntSecs();
        if( last < start_secs )
            result->addInterval(last, start_secs, "");
        QString note = QString("%1").arg(i);
        if( marker.note.length() )
            note += QString(" ") + marker.note;
        if( start_secs <= end_secs )
            result->addInterval(start_secs, end_secs, note );
        last = end_secs;
    }
    if (!markers.empty() && markers.last().end < result->dataPoints().size()) {
        double start_secs = result->dataPoints().last()->secs + result->recIntSecs();
        result->addInterval(last, start_secs, "");
    }

    file.close();
    return result;
}
RideFile *PolarFileReader::openRideFile(QFile &file, QStringList &errors, QList<RideFile*>*) const
{
/*
* Polar HRM file format documented at www.polar.fi/files/Polar_HRM_file%20format.pdf
*/
    QRegExp metricUnits("(km|kph|km/h)", Qt::CaseInsensitive);
    QRegExp englishUnits("(miles|mph|mp/h)", Qt::CaseInsensitive);
    bool metric = true;

    QDate date;
    QString note("");

    double version=0;

    double seconds=0;
    double distance=0;
    int interval = 0;

    bool speed = false;
    bool cadence = false;
    bool altitude = false;
    bool power = false;
    bool balance = false;
    bool pedaling_index = false;


    int recInterval = 1;

    if (!file.open(QFile::ReadOnly)) {
        errors << ("Could not open ride file: \""
                   + file.fileName() + "\"");
        return NULL;
    }

    int lineno = 1;


    double next_interval=0;
    QList<double> intervals;

    QTextStream is(&file);
    RideFile *rideFile = new RideFile();
    QString section = NULL;

    while (!is.atEnd()) {
        // the readLine() method doesn't handle old Macintosh CR line endings
        // this workaround will load the the entire file if it has CR endings
        // then split and loop through each line
        // otherwise, there will be nothing to split and it will read each line as expected.
        QString linesIn = is.readLine();
        QStringList lines = linesIn.split('\r');
        // workaround for empty lines
        if(lines.size() == 0) {
            lineno++;
            continue;
        }
        for (int li = 0; li < lines.size(); ++li) {
            QString line = lines[li];

            if (line == "") {

            }
            else if (line.startsWith("[")) {
                //fprintf(stderr, "section : %s\n", line.toAscii().constData());
                section=line;
                if (section == "[HRData]") {
                    // Some systems, like the Tacx HRM exporter, do not add an [IntTimes] section, so we need to
                    // specify that the whole ride is one big interval.
                    if (intervals.isEmpty())
                        intervals.append(seconds);
                   next_interval = intervals.at(0);
               }
            }
            else if (section == "[Params]"){
                if (line.contains("Version=")) {
                    QString versionString = QString(line);
                    versionString.remove(0,8).insert(1, ".");
                    version = versionString.toFloat();
                    rideFile->setDeviceType("Polar HRM (v"+versionString+")");

                } else if (line.contains("SMode=")) {
                    line.remove(0,6);
                    QString smode = QString(line);
                    if (smode.at(0)=='1')
                        speed = true;
                    if (smode.length()>0 && smode.at(1)=='1')
                        cadence = true;
                    if (smode.length()>1 && smode.at(2)=='1')
                        altitude = true;
                    if (smode.length()>2 && smode.at(3)=='1')
                        power = true;
                    if (smode.length()>3 && smode.at(4)=='1')
                        balance = true;
                    if (smode.length()>4 && smode.at(5)=='1')
                        pedaling_index = true;

/*
It appears that the Polar CS600 exports its data alays in metric when downloaded from the
polar software even when English units are displayed on the unit..  It also never sets
this bit low in the .hrm file.  This will have to get changed if other software downloads
this differently
*/

                    if (smode.length()>6 && smode.at(7)=='1')
                        metric = false;

                } else if (line.contains("Interval=")) {
                    recInterval = line.remove(0,9).toInt();
                    rideFile->setRecIntSecs(recInterval);
                } else if (line.contains("Date=")) {
                    line.remove(0,5);
                    date= QDate(line.left(4).toInt(),
                                line.mid(4,2).toInt(),
                                line.mid(6,2).toInt());
                } else if (line.contains("StartTime=")) {
                    line.remove(0,10);
                    QDateTime datetime(date,
                                      QTime(line.left(2).toInt(),
                                            line.mid(3,2).toInt(),
                                            line.mid(6,2).toInt()));
                    rideFile->setStartTime(datetime);
                 }


            }
            else if (section == "[Note]"){
                note.append(line);
            }
            else if (section == "[IntTimes]"){
                double int_seconds = line.left(2).toInt()*60*60+line.mid(3,2).toInt()*60+line.mid(6,3).toFloat();
                intervals.append(int_seconds);

                if (lines.size()==1) {
                   is.readLine();
                   is.readLine();
                   if (version>1.05) {
                       is.readLine();
                       is.readLine();
                   }
                } else {
                   li+=2;
                   if (version>1.05)
                      li+=2;
                }
            }
            else if (section == "[HRData]"){
                double nm=0,kph=0,watts=0,km=0,cad=0,hr=0,alt=0;
                double lrbalance=0;

                seconds += recInterval;

                int i=0;
                hr = line.section('\t', i, i).toDouble();
                i++;

                if (speed) {
                    kph = line.section('\t', i, i).toDouble()/10;
                    i++;
                }
                if (cadence) {
                    cad = line.section('\t', i, i).toDouble();
                    i++;
                }
                if (altitude) {
                    alt = line.section('\t', i, i).toDouble();
                    i++;
                }
                if (power) {
                    watts = line.section('\t', i, i).toDouble();
                    i++;
                }
                if (balance) {
                    // Power LRB + PI:  The value contains :
                    //  - Left Right Balance (LRB) and
                    //  - Pedalling Index (PI)
                    //
                    // in the following formula:
                    // value = PI * 256 + LRB   PI bits 15-8  LRB bits 7-0
                    // LRB is the value of left foot
                    // for example if LRB = 45, actual balance is L45 - 55R.
                    // PI values are percentages from 0 to 100.
                    // For example value 12857 (= 40 * 256 + 47)
                    // means: PI = 40 and LRB = 47 => L47 - 53R

                    lrbalance = line.section('\t', i, i).toInt() & 0xff;
                    i++;
                }

                distance = distance + kph/60/60*recInterval;
                km = distance;

                if (next_interval < seconds) {
                    interval = intervals.indexOf(next_interval);
                    if (intervals.count()>interval+1){
                        interval++;
                        next_interval = intervals.at(interval);
                    }
                }

                if (!metric) {
                    km *= KM_PER_MILE;
                    kph *= KM_PER_MILE;
                    alt *= METERS_PER_FOOT;
                }

                rideFile->appendPoint(seconds, cad, hr, km, kph, nm, watts, alt, 0.0, 0.0, 0.0, 0.0, RideFile::noTemp, lrbalance, interval);
	            //fprintf(stderr, " %f, %f, %f, %f, %f, %f, %f, %d\n", seconds, cad, hr, km, kph, nm, watts, alt, interval);
            }

        ++lineno;
        }
    }

    QRegExp rideTime("^.*/(\\d\\d\\d\\d)_(\\d\\d)_(\\d\\d)_"
                     "(\\d\\d)_(\\d\\d)_(\\d\\d)\\.hrm$");
    if (rideTime.indexIn(file.fileName()) >= 0) {
        QDateTime datetime(QDate(rideTime.cap(1).toInt(),
                                 rideTime.cap(2).toInt(),
                                 rideTime.cap(3).toInt()),
                           QTime(rideTime.cap(4).toInt(),
                                 rideTime.cap(5).toInt(),
                                 rideTime.cap(6).toInt()));


        rideFile->setStartTime(datetime);
    }
    file.close();

    return rideFile;
}
Exemple #23
0
void
Aerolab::setData(RideItem *_rideItem, bool new_zoom) {

  // HARD-CODED DATA: p1->kph
  double vfactor = 3.600;
  double m       =  totalMass;
  double small_number = 0.00001;

  rideItem = _rideItem;
  RideFile *ride = rideItem->ride();

  veArray.clear();
  altArray.clear();
  distanceArray.clear();
  timeArray.clear();

  if( ride ) {

    const RideFileDataPresent *dataPresent = ride->areDataPresent();
    //setTitle(ride->startTime().toString(GC_DATETIME_FORMAT));

    if( dataPresent->watts ) {

      // If watts are present, then we can fill the veArray data:
      const RideFileDataPresent *dataPresent = ride->areDataPresent();
      int npoints = ride->dataPoints().size();
      double dt = ride->recIntSecs();
      veArray.resize(dataPresent->watts ? npoints : 0);
      altArray.resize(dataPresent->alt || constantAlt ? npoints : 0);
      timeArray.resize(dataPresent->watts ? npoints : 0);
      distanceArray.resize(dataPresent->watts ? npoints : 0);

      // quickly erase old data
      veCurve->setVisible(false);
      altCurve->setVisible(false);

      // detach and re-attach the ve curve:
      veCurve->detach();
      if (!veArray.empty()) {
        veCurve->attach(this);
        veCurve->setVisible(dataPresent->watts);
      }

      // detach and re-attach the ve curve:
      bool have_recorded_alt_curve = false;
      altCurve->detach();
      if (!altArray.empty()) {
        have_recorded_alt_curve = true;
        altCurve->attach(this);
        altCurve->setVisible(dataPresent->alt || constantAlt );
      }

      // Fill the virtual elevation profile with data from the ride data:
      double t = 0.0;
      double vlast = 0.0;
      double e     = 0.0;
      arrayLength = 0;
      foreach(const RideFilePoint *p1, ride->dataPoints()) {
      if ( arrayLength == 0 )
        e = eoffset;

      timeArray[arrayLength]  = p1->secs / 60.0;
      if ( have_recorded_alt_curve ) {
          if ( constantAlt && arrayLength > 0) {
              altArray[arrayLength] = altArray[arrayLength-1];
          }
          else  {
              if ( constantAlt && !dataPresent->alt)
                  altArray[arrayLength] = 0;
              else
                altArray[arrayLength] = (context->athlete->useMetricUnits
                   ? p1->alt
                   : p1->alt * FEET_PER_METER);
          }
      }

      // Unpack:
      double power = max(0, p1->watts);
      double v     = p1->kph/vfactor;
      double headwind = v;
      if( dataPresent->headwind ) {
        headwind   = p1->headwind/vfactor;
      }
      double f     = 0.0;
      double a     = 0.0;

      // Use km data insteed of formula for file with a stop (gap).
      //d += v * dt;
      //distanceArray[arrayLength] = d/1000;

      distanceArray[arrayLength] = p1->km;



      if( v > small_number ) {
        f  = power/v;
        a  = ( v*v - vlast*vlast ) / ( 2.0 * dt * v );
      } else {
        a = ( v - vlast ) / dt;
      }

      f *= eta; // adjust for drivetrain efficiency if using a crank-based meter
      double s   = slope( f, a, m, crr, cda, rho, headwind );
      double de  = s * v * dt;

      e += de;
      t += dt;
      veArray[arrayLength] = e;

      vlast = v;

      ++arrayLength;
    }

  } else {
      veCurve->setVisible(false);
      altCurve->setVisible(false);
  }
    recalc(new_zoom);
    adjustEoffset();
  } else {
Exemple #24
0
    void compute(const RideFile *ride, const Zones *, int,
                 const HrZones *, int,
                 const QHash<QString,RideMetric*> &,
                 const Context *) {

        // LNP only makes sense for running and it needs recIntSecs > 0
        if (!ride->isRun() || ride->recIntSecs() == 0) {
            setValue(0.0);
            setCount(0);
            return;
        }

        // unconst naughty boy, get athlete's data
        RideFile *uride = const_cast<RideFile*>(ride);
        double weight = uride->getWeight();
        double height = uride->getHeight();

        int rollingwindowsize120 = 120 / ride->recIntSecs();
        int rollingwindowsize30 = 30 / ride->recIntSecs();

        double total = 0.0;
        int count = 0;

        // no point doing a rolling average if the
        // sample rate is greater than the rolling average
        // window!!
        if (rollingwindowsize30 > 1) {

            QVector<double> rollingSpeed(rollingwindowsize120);
            QVector<double> rollingSlope(rollingwindowsize120);
            QVector<double> rollingPower(rollingwindowsize30);
            int index120 = 0;
            int index30 = 0;
            double sumSpeed = 0.0;
            double sumSlope = 0.0;
            double sumPower = 0.0;
            double initial_speed = 0.0;

            // loop over the data and convert to a rolling
            // average for the given windowsize
            for (int i=0; i<ride->dataPoints().size(); i++) {

                double speed = ride->dataPoints()[i]->kph/3.6;
                sumSpeed += speed;
                sumSpeed -= rollingSpeed[index120];
                rollingSpeed[index120] = speed;
                double speed120 = sumSpeed/std::min(count+1, rollingwindowsize120); // speed rolling average

                double slope = ride->dataPoints()[i]->slope/100.0;
                sumSlope += slope;
                sumSlope -= rollingSlope[index120];
                rollingSlope[index120] = slope;
                double slope120 = sumSlope/std::min(count+1, rollingwindowsize120); // slope rolling average

                // running power based on 120sec averages
                double watts = running_power(weight, height, speed120, slope120, speed120*ride->recIntSecs(), initial_speed);
                initial_speed = speed120;

                sumPower += watts;
                sumPower -= rollingPower[index30];
                rollingPower[index30] = watts;

                total += pow(sumPower/std::min(count+1, rollingwindowsize30), 4); // raise rolling average to 4th power
                count ++;

                // move index on/round
                index120 = (index120 >= rollingwindowsize120-1) ? 0 : index120+1;
                index30 = (index30 >= rollingwindowsize30-1) ? 0 : index30+1;
            }
        }
        if (count) {
            lnp = pow(total/count, 0.25);
            secs = count * ride->recIntSecs();
        } else {
            lnp = secs = 0;
        }

        setValue(lnp);
        setCount(secs);
    }
RideFile *CsvFileReader::openRideFile(QFile &file, QStringList &errors) const
{
    QRegExp metricUnits("(km|kph|km/h)", Qt::CaseInsensitive);
    QRegExp englishUnits("(miles|mph|mp/h)", Qt::CaseInsensitive);
    bool metric;
    QDateTime startTime;

    // TODO: a more robust regex for ergomo files
    // i don't have an example with english headers
    // the ergomo CSV has two rows of headers. units are on the second row.
    /*
    ZEIT,STRECKE,POWER,RPM,SPEED,PULS,HÖHE,TEMP,INTERVAL,PAUSE
    L_SEC,KM,WATT,RPM,KM/H,BPM,METER,°C,NUM,SEC
    */
    QRegExp ergomoCSV("(ZEIT|STRECKE)", Qt::CaseInsensitive);
    bool ergomo = false;
    int unitsHeader = 1;
    int total_pause = 0;
    int currentInterval = 0;
    int prevInterval = 0;

    // TODO: with all these formats, should the logic change to a switch/case structure?
    // The iBike format CSV file has five lines of headers (data begins on line 6)
    // starting with:
    /*
    iBike,8,english
    2008,8,8,6,32,52

    {Various configuration data, recording interval at line[4][4]}
    Speed (mph),Wind Speed (mph),Power (W),Distance (miles),Cadence (RPM),Heartrate (BPM),Elevation (feet),Hill slope (%),Internal,Internal,Internal,DFPM Power,Latitude,Longitude
    */
    //  Modified the regExp string to allow for 2-digit version numbers - 23 Mar 2009, thm
    QRegExp iBikeCSV("iBike,\\d\\d?,[a-z]+", Qt::CaseInsensitive);
    bool iBike = false;
    int recInterval;

    if (!file.open(QFile::ReadOnly)) {
        errors << ("Could not open ride file: \""
                   + file.fileName() + "\"");
        return NULL;
    }
    int lineno = 1;
    QTextStream is(&file);
    RideFile *rideFile = new RideFile();
    int iBikeInterval = 0;
    bool dfpmExists   = false;
    int iBikeVersion  = 0;
    while (!is.atEnd()) {
        // the readLine() method doesn't handle old Macintosh CR line endings
        // this workaround will load the the entire file if it has CR endings
        // then split and loop through each line
        // otherwise, there will be nothing to split and it will read each line as expected.
        QString linesIn = is.readLine();
        QStringList lines = linesIn.split('\r');
        // workaround for empty lines
        if(lines.isEmpty()) {
            lineno++;
            continue;
        }
        for (int li = 0; li < lines.size(); ++li) {
            QString line = lines[li];

            if (lineno == 1) {
                if (ergomoCSV.indexIn(line) != -1) {
                    ergomo = true;
                    rideFile->setDeviceType("Ergomo CSV");
                    unitsHeader = 2;
                    ++lineno;
                    continue;
                }
                else {
                    if(iBikeCSV.indexIn(line) != -1) {
                        iBike = true;
                        rideFile->setDeviceType("iBike CSV");
                        unitsHeader = 5;
                        iBikeVersion = line.section( ',', 1, 1 ).toInt();
                        ++lineno;
                        continue;
                    }
                    rideFile->setDeviceType("PowerTap CSV");
                }
            }
            if (iBike && lineno == 2) {
                QStringList f = line.split(",");
                if (f.size() == 6) {
                    startTime = QDateTime(
                                    QDate(f[0].toInt(), f[1].toInt(), f[2].toInt()),
                                    QTime(f[3].toInt(), f[4].toInt(), f[5].toInt()));
                }
            }
            if (iBike && lineno == 4) {
                // this is the line with the iBike configuration data
                // recording interval is in the [4] location (zero-based array)
                // the trailing zeroes in the configuration area seem to be causing an error
                // the number is in the format 5.000000
                recInterval = (int)line.section(',',4,4).toDouble();
            }
            if (lineno == unitsHeader) {
                if (metricUnits.indexIn(line) != -1)
                    metric = true;
                else if (englishUnits.indexIn(line) != -1)
                    metric = false;
                else {
                    errors << "Can't find units in first line: \"" + line + "\" of file \"" + file.fileName() + "\".";
                    delete rideFile;
                    file.close();
                    return NULL;
                }
            }
            else if (lineno > unitsHeader) {
                double minutes,nm,kph,watts,km,cad,alt,hr,dfpm;
                double lat = 0.0, lon = 0.0;
                double headwind = 0.0;
                int interval;
                int pause;
                if (!ergomo && !iBike) {
                    minutes = line.section(',', 0, 0).toDouble();
                    nm = line.section(',', 1, 1).toDouble();
                    kph = line.section(',', 2, 2).toDouble();
                    watts = line.section(',', 3, 3).toDouble();
                    km = line.section(',', 4, 4).toDouble();
                    cad = line.section(',', 5, 5).toDouble();
                    hr = line.section(',', 6, 6).toDouble();
                    interval = line.section(',', 7, 7).toInt();
                    alt = line.section(',', 8, 8).toDouble();
                    if (!metric) {
                        km *= KM_PER_MILE;
                        kph *= KM_PER_MILE;
                        alt *= METERS_PER_FOOT;
                    }
                }
                else if (iBike) {
                    // this must be iBike
                    // can't find time as a column.
                    // will we have to extrapolate based on the recording interval?
                    // reading recording interval from config data in ibike csv file
                    //
                    // For iBike software version 11 or higher:
                    // use "power" field until a the "dfpm" field becomes non-zero.
                    minutes = (recInterval * lineno - unitsHeader)/60.0;
                    nm = NULL; //no torque
                    kph = line.section(',', 0, 0).toDouble();
                    dfpm = line.section( ',', 11, 11).toDouble();
                    if( iBikeVersion >= 11 && ( dfpm > 0.0 || dfpmExists ) ) {
                        dfpmExists = true;
                        watts = dfpm;
                        headwind = line.section(',', 1, 1).toDouble();
                    }
                    else {
                        watts = line.section(',', 2, 2).toDouble();
                    }
                    km = line.section(',', 3, 3).toDouble();
                    cad = line.section(',', 4, 4).toDouble();
                    hr = line.section(',', 5, 5).toDouble();
                    alt = line.section(',', 6, 6).toDouble();
                    lat = line.section(',', 12, 12).toDouble();
                    lon = line.section(',', 13, 13).toDouble();
                    int lap = line.section(',', 9, 9).toInt();
                    if (lap > 0) {
                        iBikeInterval += 1;
                        interval = iBikeInterval;
                    }
                    if (!metric) {
                        km *= KM_PER_MILE;
                        kph *= KM_PER_MILE;
                        alt *= METERS_PER_FOOT;
                        headwind *= KM_PER_MILE;
                    }
                }
                else {
                    // for ergomo formatted CSV files
                    minutes     = line.section(',', 0, 0).toDouble() + total_pause;
                    km = line.section(',', 1, 1).toDouble();
                    watts = line.section(',', 2, 2).toDouble();
                    cad = line.section(',', 3, 3).toDouble();
                    kph = line.section(',', 4, 4).toDouble();
                    hr = line.section(',', 5, 5).toDouble();
                    alt = line.section(',', 6, 6).toDouble();
                    interval = line.section(',', 8, 8).toInt();
                    if (interval != prevInterval) {
                        prevInterval = interval;
                        if (interval != 0) currentInterval++;
                    }
                    if (interval != 0) interval = currentInterval;
                    pause = line.section(',', 9, 9).toInt();
                    total_pause += pause;
                    nm = NULL; // torque is not provided in the Ergomo file

                    // the ergomo records the time in whole seconds
                    // RECORDING INT. 1, 2, 5, 10, 15 or 30 per sec
                    // Time is *always* perfectly sequential.  To find pauses,
                    // you need to read the PAUSE column.
                    minutes = minutes/60.0;

                    if (!metric) {
                        km *= KM_PER_MILE;
                        kph *= KM_PER_MILE;
                        alt *= METERS_PER_FOOT;
                    }
                }

                // PT reports no data as watts == -1.
                if (watts == -1)
                    watts = 0;

                rideFile->appendPoint(minutes * 60.0, cad, hr, km,
                                      kph, nm, watts, alt, lat, lon, headwind, interval);
            }
            ++lineno;
        }
    }
    file.close();

    // To estimate the recording interval, take the median of the
    // first 1000 samples and round to nearest millisecond.
    int n = rideFile->dataPoints().size();
    n = qMin(n, 1000);
    if (n >= 2) {
        QVector<double> secs(n-1);
        for (int i = 0; i < n-1; ++i) {
            double now = rideFile->dataPoints()[i]->secs;
            double then = rideFile->dataPoints()[i+1]->secs;
            secs[i] = then - now;
        }
        std::sort(secs.begin(), secs.end());
        int mid = n / 2 - 1;
        double recint = round(secs[mid] * 1000.0) / 1000.0;
        rideFile->setRecIntSecs(recint);
    }
    // less than 2 data points is not a valid ride file
    else {
        errors << "Insufficient valid data in file \"" + file.fileName() + "\".";
        delete rideFile;
        file.close();
        return NULL;
    }

    QRegExp rideTime("^.*/(\\d\\d\\d\\d)_(\\d\\d)_(\\d\\d)_"
                     "(\\d\\d)_(\\d\\d)_(\\d\\d)\\.csv$");
    rideTime.setCaseSensitivity(Qt::CaseInsensitive);
    if (startTime != QDateTime()) {
        rideFile->setStartTime(startTime);
    }
    else if (rideTime.indexIn(file.fileName()) >= 0) {
        QDateTime datetime(QDate(rideTime.cap(1).toInt(),
                                 rideTime.cap(2).toInt(),
                                 rideTime.cap(3).toInt()),
                           QTime(rideTime.cap(4).toInt(),
                                 rideTime.cap(5).toInt(),
                                 rideTime.cap(6).toInt()));
        rideFile->setStartTime(datetime);
    } else {
        // Could be yyyyddmm_hhmmss_NAME.csv (case insensitive)
        rideTime.setPattern("(\\d\\d\\d\\d)(\\d\\d)(\\d\\d)_(\\d\\d)(\\d\\d)(\\d\\d)[^\\.]*\\.csv$");
        if (rideTime.indexIn(file.fileName()) >= 0) {
            QDateTime datetime(QDate(rideTime.cap(1).toInt(),
                                     rideTime.cap(2).toInt(),
                                     rideTime.cap(3).toInt()),
                               QTime(rideTime.cap(4).toInt(),
                                     rideTime.cap(5).toInt(),
                                     rideTime.cap(6).toInt()));
            rideFile->setStartTime(datetime);
        } else {
            qWarning("Failed to set start time");
        }
    }
    return rideFile;
}
RideFile *CsvFileReader::openRideFile(QFile &file, QStringList &errors, QList<RideFile*>*) const
{
    CsvType csvType = generic;

    QRegExp metricUnits("(km|kph|km/h)", Qt::CaseInsensitive);
    QRegExp englishUnits("(miles|mph|mp/h)", Qt::CaseInsensitive);
    QRegExp degCUnits("Temperature .*C", Qt::CaseInsensitive);
    QRegExp degFUnits("Temperature .*F", Qt::CaseInsensitive);
    bool metric = true;
    enum temperature { degF, degC, degNone };
    typedef enum temperature Temperature;
    static Temperature tempType = degNone;
    QDateTime startTime;

    // Minutes,Torq (N-m),Km/h,Watts,Km,Cadence,Hrate,ID
    // Minutes, Torq (N-m),Km/h,Watts,Km,Cadence,Hrate,ID
    // Minutes,Torq (N-m),Km/h,Watts,Km,Cadence,Hrate,ID,Altitude (m)
    QRegExp powertapCSV("Minutes,[ ]?Torq \\(N-m\\),(Km/h|MPH),Watts,(Km|Miles),Cadence,Hrate,ID", Qt::CaseInsensitive);

    // TODO: a more robust regex for ergomo files
    // i don't have an example with english headers
    // the ergomo CSV has two rows of headers. units are on the second row.
    /*
    ZEIT,STRECKE,POWER,RPM,SPEED,PULS,HÖHE,TEMP,INTERVAL,PAUSE
    L_SEC,KM,WATT,RPM,KM/H,BPM,METER,°C,NUM,SEC
    */
    QRegExp ergomoCSV("(ZEIT|STRECKE)", Qt::CaseInsensitive);

    QRegExp motoActvCSV("activity_id", Qt::CaseInsensitive);
    bool epoch_set = false;
    quint64 epoch_offset=0;
    QChar ergomo_separator;
    int unitsHeader = 1;
    int total_pause = 0;
    int currentInterval = 0;
    int prevInterval = 0;
    double lastKM=0; // when deriving distance from speed

    /* Joule 1.0
    Version,Date/Time,Km,Minutes,RPE,Tags,"Weight, kg","Work, kJ",FTP,"Sample Rate, s",Device Type,Firmware Version,Last Updated,Category 1,Category 2
    6,2012-11-27 13:40:41,0,0,0,,55.8,788,227,1,Joule,18.018,,0,
    User Name,Power Zone 1,Power Zone 2,Power Zone 3,Power Zone 4,Power Zone 5,Power Zone 6,HR Zone 1,HR Zone 2,HR Zone 3,HR Zone 4,HR Zone 5,Calc Power A,Calc Power B,Calc Power 
    ,0,0,0,0,0,0,150,160,170,180,250,0,-0,
    Minutes, Torq (N-m),Km/h,Watts,Km,Cadence,Hrate,ID,Altitude (m),Temperature (�C),"Grade, %",Latitude,Longitude,Power Calc'd,Calc Power,Right Pedal,Pedal Power %,Cad. Smoot
    0,0,0,45,0,0,69,0,62,16.7,0,0,0,0,0,0,0,
    */
    QRegExp jouleCSV("Device Type,Firmware Version", Qt::CaseInsensitive);
    QRegExp jouleMetriCSV(",Km,", Qt::CaseInsensitive);

    // TODO: with all these formats, should the logic change to a switch/case structure?
    // The iBike format CSV file has five lines of headers (data begins on line 6)
    // starting with:
    /*
    iBike,8,english
    2008,8,8,6,32,52

    {Various configuration data, recording interval at line[4][4]}
    Speed (mph),Wind Speed (mph),Power (W),Distance (miles),Cadence (RPM),Heartrate (BPM),Elevation (feet),Hill slope (%),Internal,Internal,Internal,DFPM Power,Latitude,Longitude
    */

    QRegExp iBikeCSV("iBike,\\d\\d?,[a-z]+", Qt::CaseInsensitive);
    QRegExp moxyCSV("FW Part Number:", Qt::CaseInsensitive);
    QRegExp gcCSV("secs, cad, hr, km, kph, nm, watts, alt, lon, lat, headwind, slope, temp, interval, lrbalance, lte, rte, lps, rps, smo2, thb, o2hb, hhb");
    QRegExp periCSV("mm-dd,hh:mm:ss,SmO2 Live,SmO2 Averaged,THb,Target Power,Heart Rate,Speed,Power,Cadence");
    QRegExp freemotionCSV("Stages Data", Qt::CaseInsensitive);
    QRegExp cpexportCSV("seconds, value,[ model,]* date", Qt::CaseInsensitive);


    int recInterval = 1;

    if (!file.open(QFile::ReadOnly)) {
        errors << ("Could not open ride file: \""
                   + file.fileName() + "\"");
        return NULL;
    }
    int lineno = 1;
    QTextStream is(&file);
    RideFile *rideFile = new RideFile();
    int iBikeInterval = 0;
    bool dfpmExists   = false;
    int iBikeVersion  = 0;

    int secsIndex, minutesIndex = -1;

    double precWork=0.0;

    bool eof = false;
    while (!is.atEnd() && !eof) {
        // the readLine() method doesn't handle old Macintosh CR line endings
        // this workaround will load the the entire file if it has CR endings
        // then split and loop through each line
        // otherwise, there will be nothing to split and it will read each line as expected.
        QString linesIn = is.readLine();
        QStringList lines = linesIn.split('\r');
        // workaround for empty lines
        if(lines.isEmpty()) {
            lineno++;
            continue;
        }
        for (int li = 0; li < lines.size(); ++li) {
            QString line = lines[li];

            if (line.length()==0) {
                continue;
            }

            if (lineno == 1) {
                if (ergomoCSV.indexIn(line) != -1) {
                    csvType = ergomo;
                    rideFile->setDeviceType("Ergomo");
                    rideFile->setFileFormat("Ergomo CSV (csv)");
                    unitsHeader = 2;

                    QStringList headers = line.split(';');

                    if (headers.size()>1)
                        ergomo_separator = ';';
                    else
                        ergomo_separator = ',';

                    ++lineno;
                    continue;
                }
                else if(iBikeCSV.indexIn(line) != -1) {
                    csvType = ibike;
                    rideFile->setDeviceType("iBike");
                    rideFile->setFileFormat("iBike CSV (csv)");
                    unitsHeader = 5;
                    iBikeVersion = line.section( ',', 1, 1 ).toInt();
                    ++lineno;
                    continue;
                }
                else if(motoActvCSV.indexIn(line) != -1) {
                    csvType = motoactv;
                    rideFile->setDeviceType("MotoACTV");
                    rideFile->setFileFormat("MotoACTV CSV (csv)");
                    unitsHeader = -1;
                    /* MotoACTV files are always metric */
                    metric = true;
                    ++lineno;
                    continue;
                 }
                 else if(jouleCSV.indexIn(line) != -1) {
                    csvType = joule;
                    rideFile->setDeviceType("Joule");
                    rideFile->setFileFormat("Joule CSV (csv)");
                    if(jouleMetriCSV.indexIn(line) != -1) {
                        unitsHeader = 5;
                        metric = true;
                    }
                    else { /* ? */ }
                    ++lineno;
                    continue;
                 }
                 else if(moxyCSV.indexIn(line) != -1) {
                    csvType = moxy;
                    rideFile->setDeviceType("Moxy");
                    rideFile->setFileFormat("Moxy CSV (csv)");
                    unitsHeader = 4;
                    recInterval = 1;
                    ++lineno;
                    continue;
                }
                else if(gcCSV.indexIn(line) != -1) {
                    csvType = gc;
                    rideFile->setDeviceType("GoldenCheetah");
                    rideFile->setFileFormat("GoldenCheetah CSV (csv)");
                    unitsHeader = 1;
                    recInterval = 1;
                    ++lineno;
                    continue;
               }
                else if(periCSV.indexIn(line) != -1) {
                    csvType = peripedal;
                    rideFile->setDeviceType("Peripedal");
                    rideFile->setFileFormat("Peripedal CSV (csv)");
                    unitsHeader = 1;
                    recInterval = 1;
                    ++lineno;
                    continue;
               }
               else if(powertapCSV.indexIn(line) != -1) {
                    csvType = powertap;
                    rideFile->setDeviceType("PowerTap");
                    rideFile->setFileFormat("PowerTap CSV (csv)");
                    unitsHeader = 1;
                    ++lineno;
                    continue;
               }
               else if(freemotionCSV.indexIn(line) != -1) {
                    csvType = freemotion;
                    rideFile->setDeviceType("Freemotion Bike");
                    rideFile->setFileFormat("Stages Data (csv)");
                    unitsHeader = 3;
                    ++lineno;
                    continue;
               }
               else if(cpexportCSV.indexIn(line) != -1) {
                    csvType = cpexport;
                    rideFile->setDeviceType("CP Plot Export");
                    rideFile->setFileFormat("CP Plot Export (csv)");
                    unitsHeader = 1;
                    ++lineno;
                    continue;
               }
               else {  // default
                    csvType = generic;
                    rideFile->setDeviceType("Unknow");
                    rideFile->setFileFormat("Generic CSV (csv)");
               }
            }
            if (csvType == ibike) {
                if (lineno == 2) {
                    QStringList f = line.split(",");
                    if (f.size() == 6) {
                        startTime = QDateTime(
                            QDate(f[0].toInt(), f[1].toInt(), f[2].toInt()),
                            QTime(f[3].toInt(), f[4].toInt(), f[5].toInt()));
                    }
                }
                else if (lineno == 4) {
                    // this is the line with the iBike configuration data
                    // recording interval is in the [4] location (zero-based array)
                    // the trailing zeroes in the configuration area seem to be causing an error
                    // the number is in the format 5.000000
                    recInterval = (int)line.section(',',4,4).toDouble();
                }
            }

            if (csvType == freemotion) {
                if (lineno == 2) {
                    if (line == "English")
                        metric = false;
                }
            }



            if (csvType == joule && lineno == 2) {
                // 6,2012-11-27 13:40:41,0,0,0,,55.8,788,227,1,Joule,18.018,,0,
                QStringList f = line.split(",");
                if (f.size() >= 2) {
                    int f0l;
                    QStringList f0 = f[1].split("|");
                    // new format? due to new PowerAgent version (7.5.7.34)?
                    // 6,2011-01-02 21:22:20|2011-01-02 21:22|01/02/2011 21:22|2011-01-02 21-22-20,0,0, ...

                    f0l = f0.size();
                    if (f0l >= 2) {
                       startTime = QDateTime::fromString(f0[0], "yyyy-MM-dd H:mm:ss");
                    } else {
                       startTime = QDateTime::fromString(f[1], "yyyy-MM-dd H:mm:ss");
                    }
                }
            }
            if (lineno == unitsHeader && csvType == generic) {
                QRegExp timeHeaderSecs("( )*(secs|sec|time)( )*", Qt::CaseInsensitive);
                QRegExp timeHeaderMins("( )*(min|minutes)( )*", Qt::CaseInsensitive);
                QStringList headers = line.split(",");

                QStringListIterator i(headers);

                while (i.hasNext()) {
                    QString header = i.next();
                    if (timeHeaderSecs.indexIn(header) != -1)  {
                        secsIndex = headers.indexOf(header);
                    } else if (timeHeaderMins.indexIn(header) != -1)  {
                        minutesIndex = headers.indexOf(header);
                    }
                }
            }
            else if (lineno == unitsHeader && csvType != moxy && csvType != peripedal) {
                if (metricUnits.indexIn(line) != -1)
                    metric = true;
                else if (englishUnits.indexIn(line) != -1)
                    metric = false;
                else {
                    errors << "Can't find units in first line: \"" + line + "\" of file \"" + file.fileName() + "\".";
                    delete rideFile;
                    file.close();
                    return NULL;
                }

                if (degCUnits.indexIn(line) != -1)
                    tempType = degC;
                else if (degFUnits.indexIn(line) != -1)
                    tempType = degF;

            } else if (lineno > unitsHeader) {
                double minutes=0,nm=0,kph=0,watts=0,km=0,cad=0,alt=0,hr=0,dfpm=0, seconds=0.0;
                double temp=RideFile::NoTemp;
                double slope=0.0;
                bool ok;
                double lat = 0.0, lon = 0.0;
                double headwind = 0.0;
                double lrbalance = 0.0;
                double lte = 0.0, rte = 0.0;
                double lps = 0.0, rps = 0.0;
                double smo2 = 0.0, thb = 0.0;
                double o2hb = 0.0, hhb = 0.0;
                int interval=0;
                int pause=0;


                quint64 ms;

                if (csvType == powertap || csvType == joule) {
                     minutes = line.section(',', 0, 0).toDouble();
                     nm = line.section(',', 1, 1).toDouble();
                     kph = line.section(',', 2, 2).toDouble();
                     watts = line.section(',', 3, 3).toDouble();
                     km = line.section(',', 4, 4).toDouble();
                     cad = line.section(',', 5, 5).toDouble();
                     hr = line.section(',', 6, 6).toDouble();
                     interval = line.section(',', 7, 7).toInt();
                     alt = line.section(',', 8, 8).toDouble();
                    if (csvType == joule && tempType != degNone) {
                        // is the position always the same?
                        // should we read the header and assign positions
                        // to each item instead?
                        temp = line.section(',', 9, 9).toDouble();
                        if (tempType == degF) {
                           // convert to deg C
                           temp *= FAHRENHEIT_PER_CENTIGRADE + FAHRENHEIT_ADD_CENTIGRADE;
                        }
                    }
                    if (!metric) {
                        km *= KM_PER_MILE;
                        kph *= KM_PER_MILE;
                        alt *= METERS_PER_FOOT;
                    }

                } else if (csvType == gc) {
                    // GoldenCheetah CVS Format "secs, cad, hr, km, kph, nm, watts, alt, lon, lat, headwind, slope, temp, interval, lrbalance, lte, rte, lps, rps, smo2, thb, o2hb, hhb\n";

                    seconds = line.section(',', 0, 0).toDouble();
                    minutes = seconds / 60.0f;
                    cad = line.section(',', 1, 1).toDouble();
                    hr = line.section(',', 2, 2).toDouble();
                    km = line.section(',', 3, 3).toDouble();
                    kph = line.section(',', 4, 4).toDouble();
                    nm = line.section(',', 5, 5).toDouble();
                    watts = line.section(',', 6, 6).toDouble();
                    alt = line.section(',', 7, 7).toDouble();
                    lon = line.section(',', 8, 8).toDouble();
                    lat = line.section(',', 9, 9).toDouble();
                    headwind = line.section(',', 10, 10).toDouble();
                    slope = line.section(',', 11, 11).toDouble();
                    temp = line.section(',', 12, 12).toDouble();
                    interval = line.section(',', 13, 13).toInt();
                    lrbalance = line.section(',', 14, 14).toInt();
                    lte = line.section(',', 15, 15).toInt();
                    rte = line.section(',', 16, 16).toInt();
                    lps = line.section(',', 17, 17).toInt();
                    rps = line.section(',', 18, 18).toInt();
                    smo2 = line.section(',', 19, 19).toInt();
                    thb = line.section(',', 20, 20).toInt();
                    o2hb = line.section(',', 21, 21).toInt();
                    hhb = line.section(',', 22, 22).toInt();

                } else if (csvType == peripedal) {

                    //mm-dd,hh:mm:ss,SmO2 Live,SmO2 Averaged,THb,Target Power,Heart Rate,Speed,Power,Cadence
                    // ignore lines with wrong number of entries
                    if (line.split(",").count() != 10) continue;

                    seconds = moxySeconds(line.section(',',1,1));
                    minutes = seconds / 60.0f;

                    if (startTime == QDateTime()) {
                        QDate date = periDate(line.section(',',0,0));
                        QTime time = QTime(0,0,0).addSecs(seconds);
                        startTime = QDateTime(date,time);
                    }

                    double aSmo2 = line.section(',', 3, 3).toDouble();
                    smo2 = line.section(',', 2, 2).toDouble();

                    // use average if live not available
                    if (aSmo2 && !smo2) smo2 = aSmo2;

                    thb = line.section(',', 4, 4).toDouble();
                    hr = line.section(',', 6, 6).toDouble();
                    kph = line.section(',', 7, 7).toDouble();
                    watts = line.section(',', 8, 8).toDouble();
                    cad = line.section(',', 10, 10).toDouble();

                    // dervice distance from speed
                    km = lastKM + (kph/3600.0f);
                    lastKM = km;

                    nm = 0;
                    alt = 0;
                    lon = 0;
                    lat = 0;
                    headwind = 0;
                    slope = 0;
                    temp = 0;
                    interval = 0;
                    lrbalance = 0;
                    lte = 0;
                    rte = 0;
                    lps = 0;
                    rps = 0;
                    o2hb = 0;
                    hhb = 0;

                } else if (csvType == freemotion) {
                    if (line == "Ride_Totals") {
                        eof = true;
                        continue;
                    }
                    // Time,Miles,MPH,Watts,HR,RPM

                    seconds = QTime::fromString(line.section(',', 0, 0), "m:s").second();
                    minutes = QTime::fromString(line.section(',', 0, 0), "m:s").minute() + seconds / 60.0f;
                    cad = line.section(',', 5, 5).toDouble();
                    hr = line.section(',', 4, 4).toDouble();
                    km = line.section(',', 1, 1).toDouble();
                    kph = line.section(',', 2, 2).toDouble();
                    watts = line.section(',', 3, 3).toDouble();

                    if (!metric) {
                        km *= KM_PER_MILE;
                        kph *= KM_PER_MILE;
                    }

               } else if (csvType == ibike) {
                    // this must be iBike
                    // can't find time as a column.
                    // will we have to extrapolate based on the recording interval?
                    // reading recording interval from config data in ibike csv file
                    //
                    // For iBike software version 11 or higher:
                    // use "power" field until a the "dfpm" field becomes non-zero.
                     minutes = (recInterval * lineno - unitsHeader)/60.0;
                     nm = 0; //no torque
                     kph = line.section(',', 0, 0).toDouble();
                     dfpm = line.section( ',', 11, 11).toDouble();
                     headwind = line.section(',', 1, 1).toDouble();
                     if( iBikeVersion >= 11 && ( dfpm > 0.0 || dfpmExists ) ) {
                         dfpmExists = true;
                         watts = dfpm;
                     }
                     else {
                         watts = line.section(',', 2, 2).toDouble();
                     }
                     km = line.section(',', 3, 3).toDouble();
                     cad = line.section(',', 4, 4).toDouble();
                     hr = line.section(',', 5, 5).toDouble();
                     alt = line.section(',', 6, 6).toDouble();
                     slope = line.section(',', 7, 7).toDouble();
                     temp = line.section(',', 8, 8).toDouble();
                     lat = line.section(',', 12, 12).toDouble();
                     lon = line.section(',', 13, 13).toDouble();


                     int lap = line.section(',', 9, 9).toInt();
                     if (lap > 0) {
                         iBikeInterval += 1;
                         interval = iBikeInterval;
                     }
                    if (!metric) {
                        km *= KM_PER_MILE;
                        kph *= KM_PER_MILE;
                        alt *= METERS_PER_FOOT;
                        headwind *= KM_PER_MILE;
                    }

                } else if (csvType == moxy)  {

                    // we get crappy lines with no data so ignore them
                    // I think they're supposed to be delimiters for the file
                    // content, but are just noise to us !
                    if (line == (" ,,,,,") || line == ",,,,," ||
                        line == "" || line == " ") continue;

                    // need to get time from second column and note that
                    // there will be gaps when recording drops so shouldn't
                    // assume it is a continuous stream
                    double seconds = moxySeconds(line.section(',',1,1));

                    if (startTime == QDateTime()) {
                        QDate date = moxyDate(line.section(',',0,0));
                        QTime time = QTime(0,0,0).addSecs(seconds);
                        startTime = QDateTime(date,time);
                    }

                    if (seconds >0) {
                        minutes = seconds / 60.0f;
                        smo2 = line.section(',', 2, 2).remove("\"").toDouble();
                        thb = line.section(',', 4, 4).remove("\"").toDouble();
                    }
                }
               else if(csvType == motoactv) {
                    /* MotoActv saves it all as kind of SI (m, ms, m/s, NM etc)
                     *  "double","double",.. so we need to filter out "
                     */

                    km = line.section(',', 0,0).remove("\"").toDouble()/1000;
                    hr = line.section(',', 2, 2).remove("\"").toDouble();
                    kph = line.section(',', 3, 3).remove("\"").toDouble()*3.6;

                    lat = line.section(',', 5, 5).remove("\"").toDouble();
                    /* Item 8 is crank torque, 13 is wheel torque */
                    nm = line.section(',', 8, 8).remove("\"").toDouble();

                    /* Ok there's no crank torque, try the wheel */
                    if(nm == 0.0) {
                         nm = line.section(',', 13, 13).remove("\"").toDouble();
                    }
                    if(epoch_set == false) {
                         epoch_set = true;
                         epoch_offset = line.section(',', 9,9).remove("\"").toULongLong(&ok, 10);

                         /* We use this first value as the start time */
                         startTime = QDateTime();
                         startTime.setMSecsSinceEpoch(epoch_offset);
                         rideFile->setStartTime(startTime);
                    }

                    ms = line.section(',', 9,9).remove("\"").toULongLong(&ok, 10);
                    ms -= epoch_offset;
                    seconds = ms/1000;

                    alt = line.section(',', 10, 10).remove("\"").toDouble();
                    watts = line.section(',', 11, 11).remove("\"").toDouble();
                    lon = line.section(',', 15, 15).remove("\"").toDouble();
                    cad = line.section(',', 16, 16).remove("\"").toDouble();
               }
                else if (csvType == ergomo) {
                     // for ergomo formatted CSV files
                     minutes     = line.section(ergomo_separator, 0, 0).toDouble() + total_pause;
                     QString km_string = line.section(ergomo_separator, 1, 1);
                     km_string.replace(",",".");
                     km = km_string.toDouble();
                     watts = line.section(ergomo_separator, 2, 2).toDouble();
                     cad = line.section(ergomo_separator, 3, 3).toDouble();
                     QString kph_string = line.section(ergomo_separator, 4, 4);
                     kph_string.replace(",",".");
                     kph = kph_string.toDouble();
                     hr = line.section(ergomo_separator, 5, 5).toDouble();
                     alt = line.section(ergomo_separator, 6, 6).toDouble();
                     interval = line.section(',', 8, 8).toInt();
                     if (interval != prevInterval) {
                         prevInterval = interval;
                         if (interval != 0) currentInterval++;
                     }
                     if (interval != 0) interval = currentInterval;
                     pause = line.section(ergomo_separator, 9, 9).toInt();
                     total_pause += pause;
                     nm = 0; // torque is not provided in the Ergomo file

                     // the ergomo records the time in whole seconds
                     // RECORDING INT. 1, 2, 5, 10, 15 or 30 per sec
                     // Time is *always* perfectly sequential.  To find pauses,
                     // you need to read the PAUSE column.
                     minutes = minutes/60.0;

                     if (!metric) {
                         km *= KM_PER_MILE;
                         kph *= KM_PER_MILE;
                         alt *= METERS_PER_FOOT;
                     }
                } else if (csvType == cpexport) {
                    // seconds, value, (model), date
                    seconds = line.section(',',0,0).toDouble();
                    minutes = seconds / 60.0f;

                    seconds = lineno -1 ;
                    double avgwatts = line.section(',', 1, 1).toDouble();

                    watts = avgwatts * seconds - precWork; // 1000 * 1 - 700

                    precWork = avgwatts * seconds;
               } else {
                    if (secsIndex > -1) {
                        seconds = line.section(',', secsIndex, secsIndex).toDouble();
                        minutes = seconds / 60.0f;
                     }
                }

                // PT reports no data as watts == -1.
                if (watts == -1)
                    watts = 0;

               if(csvType == motoactv)
                    rideFile->appendPoint(seconds, cad, hr, km,
                                          kph, nm, watts, alt, lon, lat, 0.0,
                                          0.0, temp, 0.0, 0.0, 0.0, 0.0, 0.0,
                                          0.0, 0.0, 0.0, 0.0,
                                          0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                                          0.0, 0.0, 0.0, 0.0, 0.0, interval);
               else if (csvType == moxy) {

                    // hack it in for now
                    // XXX IT COULD BE RECORDED WITH DIFFERENT INTERVALS XXX
                    rideFile->appendPoint(minutes * 60.0, cad, hr, km,
                                          kph, nm, watts, alt, lon, lat,
                                          headwind, slope, temp, 0.0,
                                          0.0, 0.0, 0.0, 0.0,
                                          0.0, 0.0,
                                          0.0, 0.0, 0.0, 0.0,
                                          0.0, 0.0, 0.0, 0.0,
                                          smo2, thb, 0.0, 0.0, 0.0, interval);
                    rideFile->appendPoint((minutes * 60.0)+1, cad, hr, km, // dupe it so we have 1s recording easier to merge
                                          kph, nm, watts, alt, lon, lat,
                                          headwind, slope, temp, 0.0,
                                          0.0, 0.0, 0.0, 0.0,
                                          0.0, 0.0,
                                          0.0, 0.0, 0.0, 0.0,
                                          0.0, 0.0, 0.0, 0.0,
                                          smo2, thb, 0.0, 0.0, 0.0, interval);

               } else {
                    rideFile->appendPoint(minutes * 60.0, cad, hr, km,
                                          kph, nm, watts, alt, lon, lat,
                                          headwind, slope, temp, lrbalance,
                                          lte, rte, lps, rps,
                                          0.0, 0.0,
                                          0.0, 0.0, 0.0, 0.0,
                                          0.0, 0.0, 0.0, 0.0,
                                          smo2, thb, 0.0, 0.0, 0.0, interval);
               }
            }
            ++lineno;
        }
    }
    file.close();

    // To estimate the recording interval, take the median of the
    // first 1000 samples and round to nearest millisecond.
    int n = rideFile->dataPoints().size();
    n = qMin(n, 1000);
    if (n >= 2) {
        QVector<double> secs(n-1);
        for (int i = 0; i < n-1; ++i) {
            double now = rideFile->dataPoints()[i]->secs;
            double then = rideFile->dataPoints()[i+1]->secs;
            secs[i] = then - now;
        }
        std::sort(secs.begin(), secs.end());
        int mid = n / 2 - 1;
        double recint = round(secs[mid] * 1000.0) / 1000.0;
        rideFile->setRecIntSecs(recint);
    }
    // less than 2 data points is not a valid ride file
    else {
        errors << "Insufficient valid data in file \"" + file.fileName() + "\".";
        delete rideFile;
        file.close();
        return NULL;
    }

    QRegExp rideTime("^.*/(\\d\\d\\d\\d)_(\\d\\d)_(\\d\\d)_"
                     "(\\d\\d)_(\\d\\d)_(\\d\\d)\\.csv$");
    rideTime.setCaseSensitivity(Qt::CaseInsensitive);

    if (startTime != QDateTime()) {

        // Start time was already set above?
        rideFile->setStartTime(startTime);

    } else if (rideTime.indexIn(file.fileName()) >= 0) {

        // It matches the GC naming convention?
        QDateTime datetime(QDate(rideTime.cap(1).toInt(),
                                 rideTime.cap(2).toInt(),
                                 rideTime.cap(3).toInt()),
                           QTime(rideTime.cap(4).toInt(),
                                 rideTime.cap(5).toInt(),
                                 rideTime.cap(6).toInt()));
        rideFile->setStartTime(datetime);

    } else {

        // Could be yyyyddmm_hhmmss_NAME.csv (case insensitive)
        rideTime.setPattern("(\\d\\d\\d\\d)(\\d\\d)(\\d\\d)_(\\d\\d)(\\d\\d)(\\d\\d)[^\\.]*\\.csv$");
        if (rideTime.indexIn(file.fileName()) >= 0) {
            QDateTime datetime(QDate(rideTime.cap(1).toInt(),
                                     rideTime.cap(2).toInt(),
                                     rideTime.cap(3).toInt()),
                               QTime(rideTime.cap(4).toInt(),
                                     rideTime.cap(5).toInt(),
                                     rideTime.cap(6).toInt()));
            rideFile->setStartTime(datetime);
        } else {

            // is it in poweragent format "name yyyy-mm-dd hh-mm-ss.csv"
            rideTime.setPattern("(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d) (\\d\\d)-(\\d\\d)-(\\d\\d)\\.csv$");
            if (rideTime.indexIn(file.fileName()) >=0) {

                QDateTime datetime(QDate(rideTime.cap(1).toInt(),
                                        rideTime.cap(2).toInt(),
                                        rideTime.cap(3).toInt()),
                                QTime(rideTime.cap(4).toInt(),
                                        rideTime.cap(5).toInt(),
                                        rideTime.cap(6).toInt()));
                rideFile->setStartTime(datetime);

            } else {

                // NO DICE

                // Note: qWarning("Failed to set start time");
                // console messages are no use, so commented out
                // this problem will ONLY occur during the import
                // process which traps these and corrects them
                // so no need to do anything here

            }
        }
    }

    // did we actually read any samples?
    if (rideFile->dataPoints().count() > 0) {
        return rideFile;
    } else {
        errors << "No samples present.";
        delete rideFile;
        return NULL;
    }
}
    int read_page()
    {
        int sum = 0;
        int bytes_read = 0;

        char record_command = read_bytes(1, &bytes_read, &sum); // Always 0x89

        if ((0xff & record_command) == 0x89)
        {
            // 1. page # data
            int page_number = read_bytes(2, &bytes_read, &sum); // Page #
            int data_number = read_bytes(1, &bytes_read, &sum); // # of data in page



            if (page_number == 1 || (page_number == 64010 and secs == 0.0)) {
                // 2. Training Summary data (60 bytes)";
                read_bytes(39, &bytes_read, &sum);

                int record_training_flag = read_bytes(1, &bytes_read, &sum); // Training Flag

                if ((record_training_flag & 0x01) == 0) {
                    // Only new lap
                    rideFile->addInterval(last_interval_secs, secs, QString("%1").arg(interval));
                    last_interval_secs = secs;
                    interval ++;
                }

                read_bytes(20, &bytes_read, &sum); // Don't care
            }

            if (page_number == 1 || (page_number == 64010 and secs == 0.0)) {
                // Section Start time and date data (12 byte)

                int sec = read_bsd_byte(&bytes_read, &sum); // Section start time sec
                int min = read_bsd_byte(&bytes_read, &sum); // Section start time min
                int hour = read_bsd_byte(&bytes_read, &sum); // Section start time hour
                int day = read_bsd_byte(&bytes_read, &sum); // Section start time day
                int month = read_bytes(1, &bytes_read, &sum); // Section start time month
                int year = read_bsd_byte(&bytes_read, &sum); // Section start time year

                QDateTime t = QDateTime(QDate(2000+year,month,day), QTime(hour,min,sec));

                if (secs == 0.0 || rideFile->startTime().toTime_t()<0) {
                    rideFile->setStartTime(t);
                }
                else {
                    int gap = (t.toTime_t() - rideFile->startTime().toTime_t()) - secs;
                    secs += gap;
                }

                read_bytes(5, &bytes_read, &sum); // Don't care

                read_bytes(1, &bytes_read, &sum); // Data Flag
                data_number--;
            }

            for (int i = 0; i < data_number; ++i) {
                read_graph_data(&secs, &bytes_read, &sum);
            }

            int finish = 259-bytes_read;

            for (int i = 0; i < finish; i++) {
                read_bytes(1, &bytes_read, &sum); // to finish
            }

            read_bytes(1, &bytes_read, &sum); // Checksum
        }

        return bytes_read;
    }
RideFile *PolarFileReader::openRideFile(QFile &file, QStringList &errors, QList<RideFile*>*rideList) const
{
/*
* Polar HRM file format documented at www.polar.fi/files/Polar_HRM_file%20format.pdf
*/
    QRegExp metricUnits("(km|kph|km/h)", Qt::CaseInsensitive);
    QRegExp englishUnits("(miles|mph|mp/h)", Qt::CaseInsensitive);
    bool metric = true;

    QDate date;
    QString note("");

    double version=0;
    int monitor=0;

    double seconds=0;
    double distance=0;
    int interval = 0;
    int StartDelay = 0;

    bool speed = false;
    bool cadence = false;
    bool altitude = false;
    bool power = false;
    bool balance = false;
    bool haveGPX = false;
    int igpx = 0;
    int ngpx = 0;
    double lat=0,lon=0;
    int recInterval = 1;

    // Read Polar GPX file (if exist with same name as hrm file).
    RideFile *gpxresult=NULL;
    RideFilePoint *p;
    QString suffix = file.fileName();
    int dot = suffix.lastIndexOf(".");
    assert(dot >= 0);
    QFile gpxfile(suffix.left(dot)+".gpx");
    haveGPX = gpxfile.exists();

    if (haveGPX) {
        GpxFileReader reader;
        gpxresult = reader.openRideFile(gpxfile,errors,rideList);
        ngpx = gpxresult->dataPoints().count();
    }

    if (!file.open(QFile::ReadOnly)) {
        errors << ("Could not open ride file: \""
                   + file.fileName() + "\"");
        return NULL;
    }

    int lineno = 1;


    double next_interval=0;
    QList<double> intervals;

    QTextStream is(&file);
    RideFile *rideFile = new RideFile();
    QString section = NULL;

    while (!is.atEnd()) {
        // the readLine() method doesn't handle old Macintosh CR line endings
        // this workaround will load the the entire file if it has CR endings
        // then split and loop through each line
        // otherwise, there will be nothing to split and it will read each line as expected.
        QString linesIn = is.readLine();
        QStringList lines = linesIn.split('\r');
        // workaround for empty lines
        if(lines.size() == 0) {
            lineno++;
            continue;
        }
        for (int li = 0; li < lines.size(); ++li) {
            QString line = lines[li];

            if (line == "") {

            }
            else if (line.startsWith("[")) {
                //fprintf(stderr, "section : %s\n", line.toAscii().constData());
                section=line;
                if (section == "[HRData]") {
                    // Some systems, like the Tacx HRM exporter, do not add an [IntTimes] section, so we need to
                    // specify that the whole ride is one big interval.
                    if (intervals.isEmpty())
                        intervals.append(seconds);
                   next_interval = intervals.at(0);
               }
            }
            else if (section == "[Params]"){
                if (line.contains("Version=")) {
                    QString versionString = QString(line);
                    versionString.remove(0,8).insert(1, ".");
                    version = versionString.toFloat();
                    rideFile->setFileFormat("Polar HRM v"+versionString+" (hrm)");
                } else if (line.contains("Monitor=")) {
                    QString monitorString = QString(line);
                    monitorString.remove(0,8);
                    monitor = monitorString.toInt();
                    switch (monitor) {
                        case 1: rideFile->setDeviceType("Polar Sport Tester / Vantage XL"); break;
                        case 2: rideFile->setDeviceType("Polar Vantage NV (VNV)"); break;
                        case 3: rideFile->setDeviceType("Polar Accurex Plus"); break;
                        case 4: rideFile->setDeviceType("Polar XTrainer Plus"); break;
                        case 6: rideFile->setDeviceType("Polar S520"); break;
                        case 7: rideFile->setDeviceType("Polar Coach"); break;
                        case 8: rideFile->setDeviceType("Polar S210"); break;
                        case 9: rideFile->setDeviceType("Polar S410"); break;
                        case 10: rideFile->setDeviceType("Polar S510"); break;
                        case 11: rideFile->setDeviceType("Polar S610 / S610i"); break;
                        case 12: rideFile->setDeviceType("Polar S710 / S710i"); break;
                        case 13: rideFile->setDeviceType("Polar S810 / S810i"); break;
                        case 15: rideFile->setDeviceType("Polar E600"); break;
                        case 20: rideFile->setDeviceType("Polar AXN500"); break;
                        case 21: rideFile->setDeviceType("Polar AXN700"); break;
                        case 22: rideFile->setDeviceType("Polar S625X / S725X"); break;
                        case 23: rideFile->setDeviceType("Polar S725"); break;
                        case 33: rideFile->setDeviceType("Polar CS400"); break;
                        case 34: rideFile->setDeviceType("Polar CS600X"); break;
                        case 35: rideFile->setDeviceType("Polar CS600"); break;
                        case 36: rideFile->setDeviceType("Polar RS400"); break;
                        case 37: rideFile->setDeviceType("Polar RS800"); break;
                        case 38: rideFile->setDeviceType("Polar RS800X"); break;

                        default: rideFile->setDeviceType(QString("Unknown Polar Device %1").arg(monitor));
                   }
                } else if (line.contains("SMode=")) {
                    line.remove(0,6);
                    QString smode = QString(line);
                    if (smode.at(0)=='1')
                        speed = true;
                    if (smode.length()>0 && smode.at(1)=='1')
                        cadence = true;
                    if (smode.length()>1 && smode.at(2)=='1')
                        altitude = true;
                    if (smode.length()>2 && smode.at(3)=='1')
                        power = true;
                    if (smode.length()>3 && smode.at(4)=='1')
                        balance = true;
                    //if (smode.length()>4 && smode.at(5)=='1') pedaling_index = true;

                    //
                    // It appears that the Polar CS600 exports its data alays in metric when downloaded from the
                    // polar software even when English units are displayed on the unit..  It also never sets
                    // this bit low in the .hrm file.  This will have to get changed if other software downloads
                    // this differently
                    //

                    if (smode.length()>6 && smode.at(7)=='1')
                        metric = false;

                } else if (line.contains("Interval=")) {

                    recInterval = line.remove(0,9).toInt();

                    if (recInterval==238) {

                        /* This R-R data */
                        rideFile->setRecIntSecs(1);

                    } else {

                        rideFile->setRecIntSecs(recInterval);
                    }

                } else if (line.contains("Date=")) {
                    line.remove(0,5);
                    date= QDate(line.left(4).toInt(),
                                line.mid(4,2).toInt(),
                                line.mid(6,2).toInt());
                } else if (line.contains("StartTime=")) {
                    line.remove(0,10);
                    QDateTime datetime(date,
                                      QTime(line.left(2).toInt(),
                                            line.mid(3,2).toInt(),
                                            line.mid(6,2).toInt()));
                    rideFile->setStartTime(datetime);

                } else if (line.contains("StartDelay=")) {

                    StartDelay = line.remove(0,11).toInt();

                    if (recInterval==238) {
                        seconds = StartDelay/1000.0;
                    } else {
                        seconds = recInterval;
                    }
                }

            } else if (section == "[Note]") {

                note.append(line);

            } else if (section == "[IntTimes]") {

                double int_seconds = line.left(2).toInt()*60*60+line.mid(3,2).toInt()*60+line.mid(6,3).toFloat();
                intervals.append(int_seconds);

                if (lines.size()==1) {
                   is.readLine();
                   is.readLine();
                   if (version>1.05) {
                       is.readLine();
                       is.readLine();
                   }
                } else {
                   li+=2;
                   if (version>1.05)
                      li+=2;
                }

            } else if (section == "[HRData]") {

                double nm=0,kph=0,watts=0,km=0,cad=0,hr=0,alt=0,hrm=0;
                double lrbalance=RideFile::NA;

                int i=0;
                hrm = line.section('\t', i, i).toDouble();
                i++;

                if (speed) {
                    kph = line.section('\t', i, i).toDouble()/10;
                    distance += kph/60/60*recInterval;
                    km = distance;
                    i++;
                }
                if (cadence) {
                    cad = line.section('\t', i, i).toDouble();
                    i++;
                }
                if (altitude) {
                    alt = line.section('\t', i, i).toDouble();
                    i++;
                }
                if (power) {
                    watts = line.section('\t', i, i).toDouble();
                    i++;
                }
                if (balance) {
                    // Power LRB + PI:  The value contains :
                    //  - Left Right Balance (LRB) and
                    //  - Pedaling Index (PI)
                    //
                    // in the following formula:
                    // value = PI * 256 + LRB   PI bits 15-8  LRB bits 7-0
                    // LRB is the value of left foot
                    // for example if LRB = 45, actual balance is L45 - 55R.
                    // PI values are percentages from 0 to 100.
                    // For example value 12857 (= 40 * 256 + 47)
                    // means: PI = 40 and LRB = 47 => L47 - 53R

                    lrbalance = line.section('\t', i, i).toInt() & 0xff;
                    i++;
                }

                if (next_interval < seconds) {
                    interval = intervals.indexOf(next_interval);
                    if (intervals.count()>interval+1){
                        interval++;
                        next_interval = intervals.at(interval);
                    }
                }

                if (!metric) {
                    km *= KM_PER_MILE;
                    kph *= KM_PER_MILE;
                    alt *= METERS_PER_FOOT;
                }

                if (recInterval==238){
                    hr = 60000.0/hrm;
                } else {
                    hr = hrm;
                }

                if (haveGPX && gpxresult && (igpx<ngpx)) {

                    p = gpxresult->dataPoints()[igpx];

                    // Use previous value if GPS is momentarely
                    // lost. Should have option for interpolating.
                    if (p->lat!=0.0 && p->lon!=0.0) {

                        lat = p->lat;
                        lon = p->lon;

                        // Must check if current HRM speed is zero while
                        // we have GPX speed
                        if (kph==0.0 && p->kph>1.0) {

                            kph = p->kph;
                            distance += kph/60/60*recInterval;
                            km = distance;
                        }
                    }

                    if (seconds>=p->secs) igpx += 1;
                }

                rideFile->appendPoint(seconds, cad, hr, km, kph, nm, watts, alt, lon, lat,
                                      0.0, 0.0,
                                      RideFile::NA, lrbalance,
                                      0.0, 0.0, 0.0, 0.0,
                                      0.0, 0.0,
                                      0.0, 0.0, 0.0, 0.0,
                                      0.0, 0.0, 0.0, 0.0,
                                      0.0, 0.0,
                                      0.0, 0.0, 0.0, 0.0,
                                      interval);

                // fprintf(stderr, " %f, %f, %f, %f, %f, %f, %f, %d\n", seconds, cad, hr, km, kph, nm, watts, alt, interval);
                if (recInterval==238) {
                    seconds += hrm / 1000.0;
                } else {
                    seconds += recInterval;
                }
            }

            ++lineno;
        }
    }

    rideFile->setTag("Notes", note);
    QRegExp rideTime("^.*/(\\d\\d\\d\\d)_(\\d\\d)_(\\d\\d)_"
                     "(\\d\\d)_(\\d\\d)_(\\d\\d)\\.hrm$");
    if (rideTime.indexIn(file.fileName()) >= 0) {
        QDateTime datetime(QDate(rideTime.cap(1).toInt(),
                                 rideTime.cap(2).toInt(),
                                 rideTime.cap(3).toInt()),
                           QTime(rideTime.cap(4).toInt(),
                                 rideTime.cap(5).toInt(),
                                 rideTime.cap(6).toInt()));


        rideFile->setStartTime(datetime);
    }
    file.close();

    return rideFile;
}
Exemple #29
0
void
PowerHist::setData(RideItem *_rideItem, bool force)
{
    // predefined deltas for each series
    static const double wattsDelta = 1.0;
    static const double wattsKgDelta = 0.01;
    static const double nmDelta    = 0.1;
    static const double hrDelta    = 1.0;
    static const double kphDelta   = 0.1;
    static const double cadDelta   = 1.0;

    source = Ride;

    // we set with this data already
    if (force == false && _rideItem == LASTrideItem && source == LASTsource) return;

    rideItem = _rideItem;
    if (!rideItem) return;

    RideFile *ride = rideItem->ride();

    bool hasData = ((series == RideFile::watts || series == RideFile::wattsKg) && ride->areDataPresent()->watts) ||
                   (series == RideFile::nm && ride->areDataPresent()->nm) ||
                   (series == RideFile::kph && ride->areDataPresent()->kph) ||
                   (series == RideFile::cad && ride->areDataPresent()->cad) ||
                   (series == RideFile::aPower && ride->areDataPresent()->apower) ||
                   (series == RideFile::hr && ride->areDataPresent()->hr);

    if (ride && hasData) {
        //setTitle(ride->startTime().toString(GC_DATETIME_FORMAT));

        static const int maxSize = 4096;

        // recording interval in minutes
        dt = ride->recIntSecs() / 60.0;

        wattsArray.resize(0);
        wattsZoneArray.resize(0);
        wattsKgArray.resize(0);
        aPowerArray.resize(0);
        nmArray.resize(0);
        hrArray.resize(0);
        hrZoneArray.resize(0);
        kphArray.resize(0);
        cadArray.resize(0);

        wattsSelectedArray.resize(0);
        wattsZoneSelectedArray.resize(0);
        wattsKgSelectedArray.resize(0);
        aPowerSelectedArray.resize(0);
        nmSelectedArray.resize(0);
        hrSelectedArray.resize(0);
        hrZoneSelectedArray.resize(0);
        kphSelectedArray.resize(0);
        cadSelectedArray.resize(0);

        // unit conversion factor for imperial units for selected parameters
        double torque_factor = (context->athlete->useMetricUnits ? 1.0 : 0.73756215);
        double speed_factor  = (context->athlete->useMetricUnits ? 1.0 : 0.62137119);

        foreach(const RideFilePoint *p1, ride->dataPoints()) {
            bool selected = isSelected(p1, ride->recIntSecs());

            // watts array
            int wattsIndex = int(floor(p1->watts / wattsDelta));
            if (wattsIndex >= 0 && wattsIndex < maxSize) {
                if (wattsIndex >= wattsArray.size())
                    wattsArray.resize(wattsIndex + 1);
                wattsArray[wattsIndex]++;

                if (selected) {
                    if (wattsIndex >= wattsSelectedArray.size())
                        wattsSelectedArray.resize(wattsIndex + 1);
                    wattsSelectedArray[wattsIndex]++;
                }
            }

            // watts zoned array
            const Zones *zones = rideItem->zones;
            int zoneRange = zones ? zones->whichRange(ride->startTime().date()) : -1;

            // Only calculate zones if we have a valid range and check zeroes
            if (zoneRange > -1 && (withz || (!withz && p1->watts))) {
                wattsIndex = zones->whichZone(zoneRange, p1->watts);

                if (wattsIndex >= 0 && wattsIndex < maxSize) {
                    if (wattsIndex >= wattsZoneArray.size())
                        wattsZoneArray.resize(wattsIndex + 1);
                    wattsZoneArray[wattsIndex]++;

                    if (selected) {
                        if (wattsIndex >= wattsZoneSelectedArray.size())
                            wattsZoneSelectedArray.resize(wattsIndex + 1);
                        wattsZoneSelectedArray[wattsIndex]++;
                    }
                }
            }

            // aPower array
            int aPowerIndex = int(floor(p1->apower / wattsDelta));
            if (aPowerIndex >= 0 && aPowerIndex < maxSize) {
                if (aPowerIndex >= aPowerArray.size())
                    aPowerArray.resize(aPowerIndex + 1);
                aPowerArray[aPowerIndex]++;

                if (selected) {
                    if (aPowerIndex >= aPowerSelectedArray.size())
                        aPowerSelectedArray.resize(aPowerIndex + 1);
                    aPowerSelectedArray[aPowerIndex]++;
                }
            }

            // wattsKg array
            int wattsKgIndex = int(floor(p1->watts / ride->getWeight() / wattsKgDelta));
            if (wattsKgIndex >= 0 && wattsKgIndex < maxSize) {
                if (wattsKgIndex >= wattsKgArray.size())
                    wattsKgArray.resize(wattsKgIndex + 1);
                wattsKgArray[wattsKgIndex]++;

                if (selected) {
                    if (wattsKgIndex >= wattsKgSelectedArray.size())
                        wattsKgSelectedArray.resize(wattsKgIndex + 1);
                    wattsKgSelectedArray[wattsKgIndex]++;
                }
            }

            int nmIndex = int(floor(p1->nm * torque_factor / nmDelta));
            if (nmIndex >= 0 && nmIndex < maxSize) {
                if (nmIndex >= nmArray.size())
                    nmArray.resize(nmIndex + 1);
                nmArray[nmIndex]++;

                if (selected) {
                    if (nmIndex >= nmSelectedArray.size())
                        nmSelectedArray.resize(nmIndex + 1);
                    nmSelectedArray[nmIndex]++;
                }
            }

            int hrIndex = int(floor(p1->hr / hrDelta));
            if (hrIndex >= 0 && hrIndex < maxSize) {
                if (hrIndex >= hrArray.size())
                    hrArray.resize(hrIndex + 1);
                hrArray[hrIndex]++;

                if (selected) {
                    if (hrIndex >= hrSelectedArray.size())
                        hrSelectedArray.resize(hrIndex + 1);
                    hrSelectedArray[hrIndex]++;
                }
            }

            // hr zoned array
            int hrZoneRange = context->athlete->hrZones() ? context->athlete->hrZones()->whichRange(ride->startTime().date()) : -1;

            // Only calculate zones if we have a valid range
            if (hrZoneRange > -1 && (withz || (!withz && p1->hr))) {
                hrIndex = context->athlete->hrZones()->whichZone(hrZoneRange, p1->hr);

                if (hrIndex >= 0 && hrIndex < maxSize) {
                    if (hrIndex >= hrZoneArray.size())
                        hrZoneArray.resize(hrIndex + 1);
                    hrZoneArray[hrIndex]++;

                    if (selected) {
                        if (hrIndex >= hrZoneSelectedArray.size())
                            hrZoneSelectedArray.resize(hrIndex + 1);
                        hrZoneSelectedArray[hrIndex]++;
                    }
                }
            }

            int kphIndex = int(floor(p1->kph * speed_factor / kphDelta));
            if (kphIndex >= 0 && kphIndex < maxSize) {
                if (kphIndex >= kphArray.size())
                    kphArray.resize(kphIndex + 1);
                kphArray[kphIndex]++;

                if (selected) {
                    if (kphIndex >= kphSelectedArray.size())
                        kphSelectedArray.resize(kphIndex + 1);
                    kphSelectedArray[kphIndex]++;
                }
            }

            int cadIndex = int(floor(p1->cad / cadDelta));
            if (cadIndex >= 0 && cadIndex < maxSize) {
                if (cadIndex >= cadArray.size())
                    cadArray.resize(cadIndex + 1);
                cadArray[cadIndex]++;

                if (selected) {
                    if (cadIndex >= cadSelectedArray.size())
                        cadSelectedArray.resize(cadIndex + 1);
                    cadSelectedArray[cadIndex]++;
                }
            }
        }

    } else {
void
GenerateHeatMapDialog::generateNow()
{

    double minLat = 999;
    double maxLat = -999;
    double minLon = 999;
    double maxLon = -999;
    QHash<QString, int> hash;

    // loop through the table and export all selected
    for(int i=0; i<files->invisibleRootItem()->childCount(); i++) {

        // give user a chance to abort..
        QApplication::processEvents();

        // did they?
        if (aborted == true) return; // user aborted!

        QTreeWidgetItem *current = files->invisibleRootItem()->child(i);

        // is it selected
        if (static_cast<QCheckBox*>(files->itemWidget(current,0))->isChecked()) {

            files->setCurrentItem(current); QApplication::processEvents();

            // this one then
            current->setText(4, tr("Reading...")); QApplication::processEvents();

            // open it..
            QStringList errors;
            QList<RideFile*> rides;
            QFile thisfile(QString(context->athlete->home->activities().absolutePath()+"/"+current->text(1)));
            RideFile *ride = RideFileFactory::instance().openRideFile(context, thisfile, errors, &rides);

            // open success?
            if (ride) {
                current->setText(4, tr("Writing...")); QApplication::processEvents();

                if (ride->areDataPresent()->lat == true && ride->areDataPresent()->lon == true) {
                    int lastDistance = 0;
                    foreach(const RideFilePoint *point, ride->dataPoints()) {

                        if (lastDistance < (int) (point->km * 1000) &&
                           (point->lon!=0 || point->lat!=0)) {

                            // Pick up a point max every 15m
                            lastDistance = (int) (point->km * 1000) + 15;
                            //outhtml << "  new google.maps.LatLng("<<point->lat<<", "<<point->lon<<"),\n";
                            QString lonlat = QString("%1,%2").arg(floorf(point->lat*100000)/100000).arg(floorf(point->lon*100000)/100000);
                            if (hash.contains(lonlat)) {
                                hash[lonlat] = hash[lonlat] + 1;
                            } else {
                                hash[lonlat] = 1;
                            }

                            if (minLon > point->lon) minLon = point->lon;
                            if (minLat > point->lat) minLat = point->lat;
                            if (maxLon < point->lon) maxLon = point->lon;
                            if (maxLat < point->lat) maxLat = point->lat;

                        }

                    }
                }

                delete ride; // free memory!
            // open failed
            } else {