diff --git a/nmpfilter/README.md b/nmpfilter/README.md
new file mode 100644
index 00000000..32e01966
--- /dev/null
+++ b/nmpfilter/README.md
@@ -0,0 +1,10 @@
+# nmpfilter
+
+**Purpose:**
+Splits *tm-master.nmp* into smaller .nmp files filtered by region.
+
+**usage:**
+`nmpfilter HwyDataDir MasterNMP OutputDir`, where
+* `HwyDataDir` is the path of the *hwy_data* directory in the HighwayData repository, or equivalent
+* `MasterNMP` is the path of *tm-master.nmp*
+* `OutputDir` is the directory in which to write the resulting .nmp files. Trailing slash required.
diff --git a/nmpfilter/nmpfilter.cpp b/nmpfilter/nmpfilter.cpp
new file mode 100644
index 00000000..89d26ccc
--- /dev/null
+++ b/nmpfilter/nmpfilter.cpp
@@ -0,0 +1,69 @@
+// Travel Mapping Project, Eric Bryant, 2018
+#include
+#include
+#include
+#include
+#include
+using namespace std;
+
+bool LwrCaseValid(string &RgStr)
+// Check whether a string is a valid region; convert to lower case
+{ if (RgStr == ".") return 0;
+ if (RgStr == "..") return 0;
+ if (RgStr == "_systems") return 0;
+ if (RgStr == "_boundaries") return 0;
+ for (unsigned int i = 0; i < RgStr.size(); i++)
+ if (RgStr[i] >= 'A' && RgStr[i] <= 'Z')
+ RgStr[i] += 32;
+ return 1;
+}
+
+void GetRegions(char *HwyDataDir, vector &RgList)
+// Get a list of regions by scanning subdirectories of HwyData/
+{ DIR *dir;
+ dirent *ent;
+ if ((dir = opendir (HwyDataDir)) != NULL)
+ { while ((ent = readdir (dir)) != NULL)
+ { string RgStr = ent->d_name;
+ if (LwrCaseValid(RgStr)) RgList.push_back(RgStr);
+ }
+ closedir(dir);
+ }
+ else cout << "Error opening directory " << HwyDataDir << ". (Not found?)\n";
+}
+
+string GetRootRg(string RgStr)
+// Remove dashes from region codes, to match region strings as found in Roots
+{ for (int dash = RgStr.find('-'); dash > 0; dash = RgStr.find('-'))
+ RgStr.erase(dash, 1);
+ return RgStr;
+}
+
+void filter(vector &RgList, char *MasterNMP, char *OutputDir)
+// Write .nmp files for each region
+{ for (unsigned int i = 0; i < RgList.size(); i++)
+ { ifstream master(MasterNMP);
+ if (!master)
+ { std::cout << MasterNMP << " not found\n";
+ return;
+ }
+ string pt1, pt2;
+ string outfile = OutputDir+RgList[i]+".nmp";
+ ofstream nmp(outfile.data());
+ while (getline(master, pt1) && getline(master, pt2))
+ { string RootRg = GetRootRg(RgList[i]);
+ if (pt1.substr(0, pt1.find('.')) == RootRg || pt2.substr(0, pt2.find('.')) == RootRg)
+ nmp << pt1 << '\n' << pt2 << '\n';
+ }
+ }
+}
+
+int main(int argc, char *argv[])
+{ if (argc != 4)
+ { cout << "usage: nmpfilter HwyDataDir MasterNMP OutputDir\n";
+ return 0;
+ }
+ vector RgList;
+ GetRegions(argv[1], RgList);
+ filter(RgList, argv[2], argv[3]);
+}