From: tsteven4 <13596209+tsteven4@users.noreply.github.com> Date: Tue, 25 Jul 2023 00:28:23 +0000 (-0600) Subject: improve correctness and speed of duplicate filter. (#1144) X-Git-Tag: archive/raspbian/1.10.0+ds-2+rpi1~1^2~12^2~1^2~35 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=796d517a9fc42d5a477fd05a233aa0b7c2e242ad;p=gpsbabel.git improve correctness and speed of duplicate filter. (#1144) * improve correctness and speed of duplicate filter. The duplicate filter could errouneously delete points that were not duplicates if the crc's happened to match. waypt_del(Waypoint*) is inefficent as it requires a search of the list to find the matching waypoint. Support waypt_del with iterators. * retire util_crc.cc * improve duplicate to linear complexity * polish new list creation. * Remove final remnants of 'exported' * Revert "Remove final remnants of 'exported'" This reverts commit 6996e4d7dd4b1812f922edd2c5bd9a3d1bc6fada. --------- Co-authored-by: Robert Lipe --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 14eb5ca7a..465b00573 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -182,7 +182,6 @@ set(SUPPORT src/core/xmltag.cc units.cc util.cc - util_crc.cc vecs.cc waypt.cc xmlgeneric.cc diff --git a/defs.h b/defs.h index f417e52b0..635f5ae12 100644 --- a/defs.h +++ b/defs.h @@ -492,6 +492,7 @@ public: void waypt_del(Waypoint* wpt); // a.k.a. erase() // FIXME: Generally it is inefficient to use an element pointer or reference to define the element to be deleted, use iterator instead, // and/or implement pop_back() a.k.a. removeLast(), and/or pop_front() a.k.a. removeFirst(). + iterator waypt_del(iterator it) {return erase(it);} void del_rte_waypt(Waypoint* wpt); void waypt_compute_bounds(bounds* bounds) const; Waypoint* find_waypt_by_name(const QString& name) const; @@ -1110,11 +1111,6 @@ int parse_distance(const QString& str, double* val, double scale, const char* mo int parse_speed(const char* str, double* val, double scale, const char* module); int parse_speed(const QString& str, double* val, double scale, const char* module); -/* - * From util_crc.c - */ -unsigned long get_crc32(const void* data, int datalen); - /* * Color helpers. */ diff --git a/deprecated/util_crc.cc b/deprecated/util_crc.cc new file mode 100644 index 000000000..068d7db75 --- /dev/null +++ b/deprecated/util_crc.cc @@ -0,0 +1,80 @@ +/* + Compute CRC32's. + + Copyright (C) 2002, 2003, 2004 Robert Lipe, robertlipe+source@gpsbabel.org + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + */ + +static unsigned long crc32_table[256] = { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, + 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, + 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, + 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, + 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, + 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, + 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, + 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, + 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, + 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, + 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, + 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, + 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, + 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, + 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, + 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, + 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, + 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, + 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, + 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, + 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, + 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, + 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, + 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, + 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D +}; + +unsigned long +get_crc32(const void* data, int datalen) +{ + unsigned long crc = 0xFFFFFFFF; + const auto* cp = static_cast(data); + + while (cp < (static_cast(data) + datalen)) { + crc = ((crc >> 8) & 0x00FFFFFF) ^ crc32_table[(crc ^ *cp) &0xFF]; + cp++; + } + + return (crc ^ 0xFFFFFFFF); +} diff --git a/duplicate.cc b/duplicate.cc index 82f1eb929..fb471154c 100644 --- a/duplicate.cc +++ b/duplicate.cc @@ -1,7 +1,7 @@ /* exact duplicate point filter utility. - Copyright (C) 2002-2014 Robert Lipe, robertlipe+source@gpsbabel.org + Copyright (C) 2002-2023 Robert Lipe, robertlipe+source@gpsbabel.org This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,10 +22,10 @@ #include "duplicate.h" #include // for stable_sort -#include // for snprintf -#include // for memset, strncpy #include // for QDateTime +#include // for QList, QList<>::iterator, QList<>::const_iterator +#include // for QMultiHash #include "defs.h" #include "geocache.h" // for Geocache @@ -34,54 +34,7 @@ #if FILTERS_ENABLED -DuplicateFilter::btree_node* DuplicateFilter::addnode(btree_node* tree, btree_node* newnode, btree_node** oldnode) -{ - btree_node* last = nullptr; - - if (*oldnode) { - *oldnode = nullptr; - } - - if (!tree) { - return (newnode); - } - - btree_node* tmp = tree; - - while (tmp) { - last = tmp; - if (newnode->data < tmp->data) { - tmp = tmp->right; - } else if (newnode->data > tmp->data) { - tmp = tmp->left; - } else { - if (oldnode) { - *oldnode = tmp; - } - return (nullptr); - } - } - - if (newnode->data < last->data) { - last->right = newnode; - } else { - last->left = newnode; - } - - return (tree); -} - -void DuplicateFilter::free_tree(btree_node* tree) -{ - if (tree->left) { - free_tree(tree->left); - } - if (tree->right) { - free_tree(tree->right); - } - xfree(tree); -} - +#define MYNAME "duplicate" /* It looks odd that we have different comparisons for date and index. @@ -111,83 +64,79 @@ one with the smaller index (i.e. the first of those two points that we came across while importing waypoints.) In the (common) case that we have no exported dates, the dates will all -be zero so the sort will end up being an expensive no-op (expensive -because, sadly, quicksort can be O(n^2) on presorted elements.) +be zero so the sort will end up being an expensive no-op. However, the +complexity of this filter is dominated by other concerns. */ +void DuplicateFilter::init() +{ + if (!lcopt && !snopt) { + fatal(MYNAME ": one or both of the shortname and location options are required.\n"); + } +} + void DuplicateFilter::process() { - btree_node* btmp = nullptr; - btree_node* sup_tree = nullptr; - btree_node* oldnode = nullptr; - struct { - char shortname[32]; - char lat[13]; - char lon[13]; - } dupe; - Waypoint* delwpt = nullptr; - - auto htable = *global_waypoint_list; + int delete_flag; // &delete_flag != nullptr + + auto wptlist = *global_waypoint_list; auto compare_lambda = [](const Waypoint* wa, const Waypoint* wb)->bool { return wa->gc_data->exported > wb->gc_data->exported; }; - std::stable_sort(htable.begin(), htable.end(), compare_lambda); + std::stable_sort(wptlist.begin(), wptlist.end(), compare_lambda); - for (Waypoint* waypointp : htable) { - - memset(&dupe, '\0', sizeof(dupe)); - - if (snopt) { - strncpy(dupe.shortname, CSTRc(waypointp->shortname), sizeof(dupe.shortname) - 1); - } + QMultiHash wpthash; + for (Waypoint* waypointp : wptlist) { + waypointp->extra_data = nullptr; + QString key; if (lcopt) { /* The degrees2ddmm stuff is a feeble attempt to * get everything rounded the same way in a precision * that's "close enough" for determining duplicates. */ - snprintf(dupe.lat, sizeof(dupe.lat), "%11.3f", - degrees2ddmm(waypointp->latitude)); - snprintf(dupe.lon, sizeof(dupe.lon), "%11.3f", - degrees2ddmm(waypointp->longitude)); - + key = QStringLiteral("%1%2") + .arg(degrees2ddmm(waypointp->latitude), 11, 'f', 3) + .arg(degrees2ddmm(waypointp->longitude), 11, 'f', 3); + } + + if (snopt) { + key.append(waypointp->shortname); } - unsigned long crc = get_crc32(&dupe, sizeof(dupe)); - - auto* newnode = (btree_node*)xcalloc(sizeof(btree_node), 1); - newnode->data = crc; - newnode->wpt = waypointp; - - btmp = addnode(sup_tree, newnode, &oldnode); + wpthash.insert(key, waypointp); + } - if (btmp == nullptr) { - delete delwpt; - if (correct_coords && oldnode && oldnode->wpt) { - oldnode->wpt->latitude = waypointp->latitude; - oldnode->wpt->longitude = waypointp->longitude; + const QList keys = wpthash.uniqueKeys(); + for (const auto& key : keys) { + const QList values = wpthash.values(key); + if (values.size() > 1) { + Waypoint* wptfirst = values.last(); // first inserted + if (correct_coords) { + Waypoint* wptlast = values.front(); // last inserted + wptfirst->latitude = wptlast->latitude; + wptfirst->longitude = wptlast->longitude; } - delwpt = waypointp; - waypt_del(waypointp); /* collision */ - xfree(newnode); - if (purge_duplicates && oldnode) { - if (oldnode->wpt) { - waypt_del(oldnode->wpt); - delete oldnode->wpt; - oldnode->wpt = nullptr; + for (auto it = values.cbegin(); it != values.cend(); ++it) { + Waypoint* wpt = *it; + if (purge_duplicates || (wpt != wptfirst)) { + wpt->extra_data = &delete_flag; } } - - } else { - sup_tree = btmp; } } - delete delwpt; - - if (sup_tree) { - free_tree(sup_tree); + // For lineary complexity build a new list from the points we keep. + WaypointList oldlist; + waypt_swap(oldlist); + + for (Waypoint* wpt : qAsConst(oldlist)) { + if (wpt->extra_data == nullptr) { + waypt_add(wpt); + } else { + delete wpt; + } } } diff --git a/duplicate.h b/duplicate.h index 0fd5f85ca..d8d165209 100644 --- a/duplicate.h +++ b/duplicate.h @@ -1,7 +1,7 @@ /* exact duplicate point filter utility. - Copyright (C) 2002-2014 Robert Lipe, robertlipe+source@gpsbabel.org + Copyright (C) 2002-2023 Robert Lipe, robertlipe+source@gpsbabel.org This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -37,6 +37,7 @@ public: { return &args; } + void init() override; void process() override; private: @@ -64,16 +65,6 @@ private: }, }; - struct btree_node { - btree_node* left; - btree_node* right; - unsigned long data; - Waypoint* wpt; - }; - - static btree_node* addnode(btree_node* tree, btree_node* newnode, btree_node** oldnode); - void free_tree(btree_node* tree); - }; #endif #endif // DUPLICATE_H_INCLUDED_ diff --git a/util_crc.cc b/util_crc.cc deleted file mode 100644 index 068d7db75..000000000 --- a/util_crc.cc +++ /dev/null @@ -1,80 +0,0 @@ -/* - Compute CRC32's. - - Copyright (C) 2002, 2003, 2004 Robert Lipe, robertlipe+source@gpsbabel.org - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - */ - -static unsigned long crc32_table[256] = { - 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, - 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, - 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, - 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, - 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, - 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, - 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, - 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, - 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, - 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, - 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, - 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, - 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, - 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, - 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, - 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, - 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, - 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, - 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, - 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, - 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, - 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, - 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, - 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, - 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, - 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, - 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, - 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, - 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, - 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, - 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, - 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, - 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, - 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, - 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, - 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, - 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, - 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, - 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, - 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, - 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, - 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, - 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D -}; - -unsigned long -get_crc32(const void* data, int datalen) -{ - unsigned long crc = 0xFFFFFFFF; - const auto* cp = static_cast(data); - - while (cp < (static_cast(data) + datalen)) { - crc = ((crc >> 8) & 0x00FFFFFF) ^ crc32_table[(crc ^ *cp) &0xFF]; - cp++; - } - - return (crc ^ 0xFFFFFFFF); -} diff --git a/xmldoc/filters/duplicate.xml b/xmldoc/filters/duplicate.xml index 922522f49..9ef4d4be7 100644 --- a/xmldoc/filters/duplicate.xml +++ b/xmldoc/filters/duplicate.xml @@ -4,7 +4,7 @@ short name (traditionally a waypoint's name on the GPS receiver), and/or their location (to a precision of 6 decimals). This filter supports two options that specify how duplicates will be recognized, and . -Generally, at least one of these options is required. +At least one of these options is required. Using the duplicate filter to suppress points with the same