From b67e52950a41995cf68fca44d2b8abd14cc1cf01 Mon Sep 17 00:00:00 2001 From: Jacob Pike Date: Sat, 4 Mar 2017 17:42:33 -0500 Subject: [PATCH] DynamicBibleUtility - Adding initial geolocation JSON creation. Still need to incorporate Strong's numbers. --- .../DynamicBibleUtility.csproj | 6 + .../Geolocation/BibleBook.cs | 177 +++++++++++++++++ .../Geolocation/BibleLocationIndexByName.cs | 32 +++ .../Geolocation/BibleLocationIndexByVerse.cs | 52 +++++ .../Geolocation/BibleLocationReference.cs | 61 ++++++ .../Geolocation/BibleVerseReference.cs | 31 +++ .../OpenBibleDotInfoLocationParser.cs | 187 ++++++++++++++++++ .../DynamicBibleUtility/frmMain.Designer.cs | 58 ++++-- .../DynamicBibleUtility/frmMain.cs | 73 +++++++ .../DynamicBibleUtility/frmMain.resx | 3 + 10 files changed, 664 insertions(+), 16 deletions(-) create mode 100644 DynamicBibleUtility/DynamicBibleUtility/Geolocation/BibleBook.cs create mode 100644 DynamicBibleUtility/DynamicBibleUtility/Geolocation/BibleLocationIndexByName.cs create mode 100644 DynamicBibleUtility/DynamicBibleUtility/Geolocation/BibleLocationIndexByVerse.cs create mode 100644 DynamicBibleUtility/DynamicBibleUtility/Geolocation/BibleLocationReference.cs create mode 100644 DynamicBibleUtility/DynamicBibleUtility/Geolocation/BibleVerseReference.cs create mode 100644 DynamicBibleUtility/DynamicBibleUtility/Geolocation/OpenBibleDotInfoLocationParser.cs diff --git a/DynamicBibleUtility/DynamicBibleUtility/DynamicBibleUtility.csproj b/DynamicBibleUtility/DynamicBibleUtility/DynamicBibleUtility.csproj index 1a040158..e26bb595 100644 --- a/DynamicBibleUtility/DynamicBibleUtility/DynamicBibleUtility.csproj +++ b/DynamicBibleUtility/DynamicBibleUtility/DynamicBibleUtility.csproj @@ -63,6 +63,12 @@ frmMain.cs + + + + + + diff --git a/DynamicBibleUtility/DynamicBibleUtility/Geolocation/BibleBook.cs b/DynamicBibleUtility/DynamicBibleUtility/Geolocation/BibleBook.cs new file mode 100644 index 00000000..f6b68f88 --- /dev/null +++ b/DynamicBibleUtility/DynamicBibleUtility/Geolocation/BibleBook.cs @@ -0,0 +1,177 @@ +using System.Collections.Generic; + +namespace DynamicBibleUtility.Geolocation +{ + /// + /// An enum for uniquely identifying a book of the Bible. + /// An "invalid" enum value is first so that the numeric + /// value for actual books starts at 1, which helps maintain + /// consistency with how verse references are used in other + /// data for the app. + /// + public enum BibleBookId + { + INVALID, + GENESIS, + EXODUS, + LEVITICUS, + NUMBERS, + DEUTERONOMY, + JOSHUA, + JUDGES, + RUTH, + FIRST_SAMUEL, + SECOND_SAMUEL, + FIRST_KINGS, + SECOND_KINGS, + FIRST_CHRONICLES, + SECOND_CHRONICLES, + EZRA, + NEHEMIAH, + ESTHER, + JOB, + PSALMS, + PROVERBS, + ECCLESIASTES, + SONG_OF_SOLOMON, + ISAIAH, + JEREMIAH, + LAMENTATIONS, + EZEKIEL, + DANIEL, + HOSEA, + JOEL, + AMOS, + OBADIAH, + JONAH, + MICAH, + NAHUM, + HABAKKUK, + ZEPHANIAH, + HAGGAI, + ZECHARIAH, + MALACHI, + MATTHEW, + MARK, + LUKE, + JOHN, + ACTS, + ROMANS, + FIRST_CORINTHIANS, + SECOND_CORINTHIANS, + GALATIANS, + EPHESIANS, + PHILIPPIANS, + COLOSSIANS, + FIRST_THESSALONIANS, + SECOND_THESSALONIANS, + FIRST_TIMOTHY, + SECOND_TIMOTHY, + TITUS, + PHILEMON, + HEBREWS, + JAMES, + FIRST_PETER, + SECOND_PETER, + FIRST_JOHN, + SECOND_JOHN, + THIRD_JOHN, + JUDE, + REVELATION + }; + + /// + /// A book of the Bible. + /// + public class BibleBook + { + /// The unique ID of book. + public BibleBookId Id = BibleBookId.INVALID; + + /// + /// Constructs a book by parsing a string. + /// + /// A string representation of the book. + /// Thrown if a parsing error occurs. + public BibleBook(string book_string) + { + // DEFINE A MAPPING OF BOOK STRINGS TO BOOK IDs. + // This code currently only handles parsing a subset of book strings as + // needed for geolocation parsing. If needed, it could be made more + // generic later and moved out of this namespace, but the structure + // of this logic would need to be altered. + var string_to_book_id_lookup = new Dictionary + { + { "Gen", BibleBookId.GENESIS }, + { "Ex", BibleBookId.EXODUS }, + { "Lev", BibleBookId.LEVITICUS }, + { "Num", BibleBookId.NUMBERS }, + { "Deut", BibleBookId.DEUTERONOMY }, + { "Josh", BibleBookId.JOSHUA }, + { "Judg", BibleBookId.JUDGES }, + { "Ruth", BibleBookId.RUTH }, + { "1 Sam", BibleBookId.FIRST_SAMUEL }, + { "2 Sam", BibleBookId.SECOND_SAMUEL }, + { "1 Kgs", BibleBookId.FIRST_KINGS }, + { "2 Kgs", BibleBookId.SECOND_KINGS }, + { "1 Chr", BibleBookId.FIRST_CHRONICLES }, + { "2 Chr", BibleBookId.SECOND_CHRONICLES }, + { "Ezra", BibleBookId.EZRA }, + { "Neh", BibleBookId.NEHEMIAH }, + { "Est", BibleBookId.ESTHER }, + { "Job", BibleBookId.JOB }, + { "Ps", BibleBookId.PSALMS }, + // No locations exist for Proverbs. { "", BibleBookId.PROVERBS }, + { "Eccl", BibleBookId.ECCLESIASTES }, + { "Sng", BibleBookId.SONG_OF_SOLOMON }, + { "Isa", BibleBookId.ISAIAH }, + { "Jer", BibleBookId.JEREMIAH }, + { "Lam", BibleBookId.LAMENTATIONS }, + { "Ezek", BibleBookId.EZEKIEL }, + { "Dan", BibleBookId.DANIEL }, + { "Hos", BibleBookId.HOSEA }, + { "Joel", BibleBookId.JOEL }, + { "Amos", BibleBookId.AMOS }, + { "Obad", BibleBookId.OBADIAH }, + { "Jonah", BibleBookId.JONAH }, + { "Mic", BibleBookId.MICAH }, + { "Nahum", BibleBookId.NAHUM }, + { "Hab", BibleBookId.HABAKKUK }, + { "Zeph", BibleBookId.ZEPHANIAH }, + { "Hag", BibleBookId.HAGGAI }, + { "Zech", BibleBookId.ZECHARIAH }, + { "Mal", BibleBookId.MALACHI }, + { "Matt", BibleBookId.MATTHEW }, + { "Mark", BibleBookId.MARK }, + { "Luke", BibleBookId.LUKE }, + { "John", BibleBookId.JOHN }, + { "Acts", BibleBookId.ACTS }, + { "Rom", BibleBookId.ROMANS }, + { "1 Cor", BibleBookId.FIRST_CORINTHIANS }, + { "2 Cor", BibleBookId.SECOND_CORINTHIANS }, + { "Gal", BibleBookId.GALATIANS }, + { "Eph", BibleBookId.EPHESIANS }, + { "Phil", BibleBookId.PHILIPPIANS }, + { "Col", BibleBookId.COLOSSIANS }, + { "1 Thes", BibleBookId.FIRST_THESSALONIANS }, + { "2 Thes", BibleBookId.SECOND_THESSALONIANS }, + { "1 Tim", BibleBookId.FIRST_TIMOTHY }, + { "2 Tim", BibleBookId.SECOND_TIMOTHY }, + { "Titus", BibleBookId.TITUS }, + // No locations exist for Philemon. { "", BibleBookId.PHILEMON }, + { "Heb", BibleBookId.HEBREWS }, + // No locations exist for James. { "", BibleBookId.JAMES }, + { "1 Pet", BibleBookId.FIRST_PETER }, + { "2 Pet", BibleBookId.SECOND_PETER }, + // No locations exist for 1 John. { "", BibleBookId.FIRST_JOHN }, + // No locations exist for 2 John. { "", BibleBookId.SECOND_JOHN }, + // No locations exist for 3 John. { "", BibleBookId.THIRD_JOHN }, + { "Jude", BibleBookId.JUDE }, + { "Rev", BibleBookId.REVELATION } + }; + + // GET THE BOOK ID FROM THE STRING. + this.Id = string_to_book_id_lookup[book_string]; + } + } +} diff --git a/DynamicBibleUtility/DynamicBibleUtility/Geolocation/BibleLocationIndexByName.cs b/DynamicBibleUtility/DynamicBibleUtility/Geolocation/BibleLocationIndexByName.cs new file mode 100644 index 00000000..f9f4d1c9 --- /dev/null +++ b/DynamicBibleUtility/DynamicBibleUtility/Geolocation/BibleLocationIndexByName.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +namespace DynamicBibleUtility.Geolocation +{ + /// + /// An index of Bible location data by location name. + /// + public class BibleLocationIndexByName + { + /// A mapping of location names to full location data. + public IDictionary NameToLocationLookup = new Dictionary(); + + /// + /// Creates the index from the provided locations. + /// + /// The locations to put in the index. + /// Thrown if the locations are null. + public BibleLocationIndexByName(IEnumerable locations) + { + // INDEX THE LOCATIONS BY NAME. + foreach (var location in locations) + { + // ONLY INDEX THE LOCATION IF IT HAS GEOGRAPHIC COORDINATES. + // The information currently isn't useful without these coordinates. + if (location.HasGeographicCoordinates) + { + NameToLocationLookup[location.Name] = location; + } + } + } + } +} diff --git a/DynamicBibleUtility/DynamicBibleUtility/Geolocation/BibleLocationIndexByVerse.cs b/DynamicBibleUtility/DynamicBibleUtility/Geolocation/BibleLocationIndexByVerse.cs new file mode 100644 index 00000000..382e3376 --- /dev/null +++ b/DynamicBibleUtility/DynamicBibleUtility/Geolocation/BibleLocationIndexByVerse.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; + +namespace DynamicBibleUtility.Geolocation +{ + /// + /// An index of Bible location data by verse reference. + /// + public class BibleLocationIndexByVerse + { + /// + /// A mapping of verse references to location names. + /// The verse reference keys are stored using their short string form in order + /// to simplify hashing (no custom hash code implementation) and to simplify + /// JSON serialization (so that the short string form is only serialized). + /// Only the location name, rather than the full location data, is stored + /// since the name can be used in a parallel lookup in the . + /// + public IDictionary> VerseToLocationNameLookup = new Dictionary>(); + + /// + /// Creates the index from the provided locations. + /// + /// The locations to put in the index. + /// Thrown if the locations are null. + public BibleLocationIndexByVerse(IEnumerable locations) + { + // INDEX THE LOCATIONS BY VERSE REFERENCE. + foreach (var location in locations) + { + // ONLY INDEX THE LOCATION IF IT HAS GEOGRAPHIC COORDINATES. + // The information currently isn't useful without these coordinates. + if (location.HasGeographicCoordinates) + { + // INDEX THE LOCATION NAME BY ALL ITS VERSE REFERENCES. + foreach (var verse_reference in location.VerseReferences) + { + // MAKE SURE THE VERSE HAS AN EXISTING COLLECTION FOR LOCATION DATA. + string verse_reference_string = verse_reference.ToString(); + bool verse_reference_exists_in_index = VerseToLocationNameLookup.ContainsKey(verse_reference_string); + if (!verse_reference_exists_in_index) + { + VerseToLocationNameLookup[verse_reference_string] = new List(); + } + + // ADD THE LOCATION NAME FOR THE VERSE. + VerseToLocationNameLookup[verse_reference_string].Add(location.Name); + } + } + } + } + } +} diff --git a/DynamicBibleUtility/DynamicBibleUtility/Geolocation/BibleLocationReference.cs b/DynamicBibleUtility/DynamicBibleUtility/Geolocation/BibleLocationReference.cs new file mode 100644 index 00000000..b0a4b222 --- /dev/null +++ b/DynamicBibleUtility/DynamicBibleUtility/Geolocation/BibleLocationReference.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; + +namespace DynamicBibleUtility.Geolocation +{ + /// + /// A location mentioned in the Bible, with geographic coordinates and verse references. + /// + /// Attributes are used to control serialization to minimize the amount of JSON that + /// gets serialized. + /// + public class BibleLocationReference + { + /// + /// The name of the location. + /// Ignored for serialization since the name is more useful separately + /// as a key for a property in a JavaScript object, rather than + /// being duplicated again in the serialized data. + /// + [JsonIgnore] + public string Name = ""; + /// The latitude of the location, if available. + [JsonProperty("lat")] + public double? Latitude = null; + /// The longitude of the location, if available. + [JsonProperty("lon")] + public double? Longitude = null; + /// References to verses that mention the location. + [JsonIgnore] + public IEnumerable VerseReferences = new List(); + + /// + /// Gets references to verses that mention the location, in their short string form + /// as used by the Dynamic Bible app. + /// + [JsonProperty("vss")] + public IEnumerable VerseReferenceStrings + { + get + { + var verse_reference_strings = VerseReferences.Select(verse_reference => verse_reference.ToString()); + return verse_reference_strings; + } + } + + /// + /// True if this location information has full geographic coordinates; false otherwise. + /// Marked such that it doesn't get serialized to JSON. + /// + [JsonIgnore] + public bool HasGeographicCoordinates + { + get + { + bool has_geographic_coordinates = Latitude.HasValue && Longitude.HasValue; + return has_geographic_coordinates; + } + } + } +} diff --git a/DynamicBibleUtility/DynamicBibleUtility/Geolocation/BibleVerseReference.cs b/DynamicBibleUtility/DynamicBibleUtility/Geolocation/BibleVerseReference.cs new file mode 100644 index 00000000..2e8f0a9e --- /dev/null +++ b/DynamicBibleUtility/DynamicBibleUtility/Geolocation/BibleVerseReference.cs @@ -0,0 +1,31 @@ +using System; +using Newtonsoft.Json; + +namespace DynamicBibleUtility.Geolocation +{ + /// + /// A reference to a Bible verse including a book, chapter, and verse. + /// + public class BibleVerseReference + { + /// The book of the Bible. + public BibleBook Book = null; + /// The chapter number within the book. + public int Chapter = 0; + /// The verse number within the book. + public int Verse = 0; + + /// + /// Converts the verse reference to the canonical string form + /// for the Dynamic Bible app. This form has the numeric book, + /// chapter, and verse in a single colon-separated string. + /// + /// The canonical short string form of the verse reference. + public override string ToString() + { + int book_number = Convert.ToInt32(Book.Id); + string short_reference_string = $"{book_number}:{Chapter}:{Verse}"; + return short_reference_string; + } + } +} diff --git a/DynamicBibleUtility/DynamicBibleUtility/Geolocation/OpenBibleDotInfoLocationParser.cs b/DynamicBibleUtility/DynamicBibleUtility/Geolocation/OpenBibleDotInfoLocationParser.cs new file mode 100644 index 00000000..20c98415 --- /dev/null +++ b/DynamicBibleUtility/DynamicBibleUtility/Geolocation/OpenBibleDotInfoLocationParser.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.IO; + +/// +/// A namespace for code related to geolocation data for the Dynamic Bible app. +/// +namespace DynamicBibleUtility.Geolocation +{ + /// + /// A parser for Biblical location information from openbible.info. + /// + /// Specifically, this class only handles parsing the tab-delimited + /// file from https://www.openbible.info/geo/data/merged.txt. + /// Parsing this specific file was chosen over the KMZ/KML files + /// because it was a much simpler way to get the relevant data. + /// The "merged" version of the raw data was chosen over the + /// "unmerged" version because it seemed to contain more data. + /// + /// The data parsed by this parser is licensed under the + /// Creative Commons Attribution license (see + /// https://www.openbible.info/geo/ and + /// https://creativecommons.org/licenses/by/4.0/). + /// + public class OpenBibleDotInfoLocationParser + { + /// + /// Parses Biblical location information from the specified file. + /// + /// The relative or absolute path to the file to parse. + /// Location references parsed from the file; never null. + /// Thrown if a parsing error occurs. + public static IEnumerable Parse(string filepath) + { + // READ THE ENTIRE GEOLOCATION DATA FILE. + // It is small enough to store completely in memory. + string[] geolocation_input_file_lines = File.ReadAllLines(filepath); + + // PARSE EACH LINE OF GEOLOCATION DATA. + // The first line contains a comment and the second line contains a header, + // so those two lines can be skipped. + const int FIRST_GEOLOCATION_LINE_INDEX = 2; + var locations = new List(); + for (int line_index = FIRST_GEOLOCATION_LINE_INDEX; line_index < geolocation_input_file_lines.Length; ++line_index) + { + // SPLIT THE LINE INTO SEPARATE FIELDS. + // Since empty fields sometimes exist in the actual data, empty entries are still included + // from the string splitting operation to make indexing into known fields simpler. + const char FIELD_SEPARATOR = '\t'; + string current_geolocation_line = geolocation_input_file_lines[line_index]; + string[] current_line_fields = current_geolocation_line.Split( + new char[] { FIELD_SEPARATOR }, + StringSplitOptions.None); + + // PARSE THE LOCATION INFORMATION FROM CURRENT LINE. + BibleLocationReference location = new BibleLocationReference(); + + // The name is converted to lowercase to make it easier to do + // case insensitive lookups. + const int BIBLE_LOCATION_NAME_FIELD_INDEX = 0; + location.Name = current_line_fields[BIBLE_LOCATION_NAME_FIELD_INDEX]; + location.Name = location.Name.ToLower(); + + // The file contains both the name of the location as mentioned in the Bible (parsed above) + // and this second name for the actual location that the geographic coordinates reference. + // Since the geographics coordinates are expected to be close enough to the Biblical name + // and the primary purpose of this data is to cross-reference the Biblical text, + // this second name is silently ignored but could be added later if desired. + const int GEO_COORDINATE_LOCATION_NAME_FIELD_INDEX = 1; + string geo_coordinate_location_name = current_line_fields[GEO_COORDINATE_LOCATION_NAME_FIELD_INDEX]; + + const int LATITUDE_INDEX = 2; + string latitude_string = current_line_fields[LATITUDE_INDEX]; + location.Latitude = ParseGeographicCoordinate(latitude_string); + + const int LONGITUDE_INDEX = 3; + string longitude_string = current_line_fields[LONGITUDE_INDEX]; + location.Longitude = ParseGeographicCoordinate(longitude_string); + + const int VERSE_REFERENCES_INDEX = 4; + string verse_references_csv_list = current_line_fields[VERSE_REFERENCES_INDEX]; + location.VerseReferences = ParseVerseReferences(verse_references_csv_list); + + // ADD THE LOCATION INFORMATION FOR RETURNING. + locations.Add(location); + } + + return locations; + } + + /// + /// Attempts to parse a geographic coordinate from the specified string. + /// This method is necessary because not all coordinate values in the file + /// are necessarily completely numeric. + /// + /// The coordinate string to parse. + /// + /// The geographic coordinate, if successfully parsed. + /// Null only if no geographic coordinate exists (an exception is thrown + /// if an unexpected parsing error occurs in order to provide easier visibilty + /// into such errors). + /// + /// Thrown if a parsing error occurs. + private static double? ParseGeographicCoordinate(string coordinate_string) + { + // REMOVE ANY KNOWN NON-NUMERIC CHARACTERS FROM THE STRING. + // These characters are used to mark cases where the location isn't known + // or the location may not be exact. That exactness isn't super important + // in this context, so the "marker" characters are ignored. + string numeric_coordinate_string = coordinate_string.Trim('?', '~', '<', '>'); + + // A '-' is used sometimes to indicate no location. Since a '-' could also + // be used for a negative geographic coordinate, it can only be safely + // trimmed from the end. + numeric_coordinate_string = numeric_coordinate_string.TrimEnd('-'); + + // CHECK IF A COORDINATE EXISTS. + bool coordinate_exists = !string.IsNullOrWhiteSpace(numeric_coordinate_string); + if (!coordinate_exists) + { + // Not all locations in this file may have geographic coordinates. + return null; + } + + // PARSE THE NUMERIC COORDINATE. + double coordinate = double.Parse(numeric_coordinate_string); + return coordinate; + } + + /// + /// Attempts to parse Bible verse references from a CSV list. + /// + /// A CSV list of Bible verse references. + /// Each reference is expected to be separated by a comma OR a comma and single space. + /// The verse references from the string; an empty list if no verse references exist in the string. + /// Thrown if a parsing error occurs. + private static IEnumerable ParseVerseReferences(string verse_references_csv_list) + { + // GET THE INDIVIDUAL VERSE REFERENCE STRINGS FROM THE LIST. + string[] verse_reference_strings = verse_references_csv_list.Split( + new string[] { ", ", "," }, + StringSplitOptions.RemoveEmptyEntries); + + // PARSE EACH VERSE REFERENCE. + var verse_references = new List(); + foreach (string verse_reference_string in verse_reference_strings) + { + // PARSE THE BOOK. + // A single space separates the book from the chapter and verse numbers. + // Since there might be an additional space before that separator + // for books with numbers at the start, a split can't be used directly. + const int BOOK_START_INDEX = 0; + int index_of_space_after_book = verse_reference_string.LastIndexOf(' '); + int book_string_length_in_characters = index_of_space_after_book; + string book_string = verse_reference_string.Substring(BOOK_START_INDEX, book_string_length_in_characters); + BibleBook book = new BibleBook(book_string); + + // PARSE THE CHAPTER. + // A single colon separates the chapter and verse numbers. + int chapter_start_index = index_of_space_after_book + 1; + string chapter_and_verse_string = verse_reference_string.Substring(chapter_start_index); + string[] chapter_and_verse_numbers = chapter_and_verse_string.Split( + new char[] { ':' }, + StringSplitOptions.RemoveEmptyEntries); + const int CHAPTER_INDEX = 0; + string chapter_string = chapter_and_verse_numbers[CHAPTER_INDEX]; + int chapter = int.Parse(chapter_string); + + // PARSE THE VERSE. + const int VERSE_INDEX = 1; + string verse_string = chapter_and_verse_numbers[VERSE_INDEX]; + int verse = int.Parse(verse_string); + + // ADD THE PARSED THE BIBLE VERSE REFERENCE. + BibleVerseReference verse_reference = new BibleVerseReference + { + Book = book, + Chapter = chapter, + Verse = verse + }; + verse_references.Add(verse_reference); + } + + return verse_references; + } + } +} diff --git a/DynamicBibleUtility/DynamicBibleUtility/frmMain.Designer.cs b/DynamicBibleUtility/DynamicBibleUtility/frmMain.Designer.cs index 65a8d415..52d37069 100644 --- a/DynamicBibleUtility/DynamicBibleUtility/frmMain.Designer.cs +++ b/DynamicBibleUtility/DynamicBibleUtility/frmMain.Designer.cs @@ -28,6 +28,7 @@ /// private void InitializeComponent() { + this.components = new System.ComponentModel.Container(); this.btnCreateIndex = new System.Windows.Forms.Button(); this.txtStatus = new System.Windows.Forms.TextBox(); this.btnCreateText = new System.Windows.Forms.Button(); @@ -35,13 +36,16 @@ this.btnCreateStrongsDict = new System.Windows.Forms.Button(); this.btnCreateRMAC = new System.Windows.Forms.Button(); this.btnRmacCrossRefs = new System.Windows.Forms.Button(); + this.btnCreateGeolocationJson = new System.Windows.Forms.Button(); + this.createGeolocationJsonTooltip = new System.Windows.Forms.ToolTip(this.components); this.SuspendLayout(); // // btnCreateIndex // - this.btnCreateIndex.Location = new System.Drawing.Point(12, 12); + this.btnCreateIndex.Location = new System.Drawing.Point(16, 15); + this.btnCreateIndex.Margin = new System.Windows.Forms.Padding(4); this.btnCreateIndex.Name = "btnCreateIndex"; - this.btnCreateIndex.Size = new System.Drawing.Size(75, 23); + this.btnCreateIndex.Size = new System.Drawing.Size(100, 28); this.btnCreateIndex.TabIndex = 0; this.btnCreateIndex.Text = "Create Index"; this.btnCreateIndex.UseVisualStyleBackColor = true; @@ -52,17 +56,19 @@ this.txtStatus.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.txtStatus.Location = new System.Drawing.Point(13, 42); + this.txtStatus.Location = new System.Drawing.Point(17, 52); + this.txtStatus.Margin = new System.Windows.Forms.Padding(4); this.txtStatus.Multiline = true; this.txtStatus.Name = "txtStatus"; - this.txtStatus.Size = new System.Drawing.Size(734, 507); + this.txtStatus.Size = new System.Drawing.Size(1147, 623); this.txtStatus.TabIndex = 1; // // btnCreateText // - this.btnCreateText.Location = new System.Drawing.Point(93, 12); + this.btnCreateText.Location = new System.Drawing.Point(124, 15); + this.btnCreateText.Margin = new System.Windows.Forms.Padding(4); this.btnCreateText.Name = "btnCreateText"; - this.btnCreateText.Size = new System.Drawing.Size(75, 23); + this.btnCreateText.Size = new System.Drawing.Size(100, 28); this.btnCreateText.TabIndex = 2; this.btnCreateText.Text = "CreateText"; this.btnCreateText.UseVisualStyleBackColor = true; @@ -70,9 +76,10 @@ // // btnCreateStrongs // - this.btnCreateStrongs.Location = new System.Drawing.Point(174, 12); + this.btnCreateStrongs.Location = new System.Drawing.Point(232, 15); + this.btnCreateStrongs.Margin = new System.Windows.Forms.Padding(4); this.btnCreateStrongs.Name = "btnCreateStrongs"; - this.btnCreateStrongs.Size = new System.Drawing.Size(144, 23); + this.btnCreateStrongs.Size = new System.Drawing.Size(192, 28); this.btnCreateStrongs.TabIndex = 3; this.btnCreateStrongs.Text = "Create Strongs Cross Refs"; this.btnCreateStrongs.UseVisualStyleBackColor = true; @@ -80,9 +87,10 @@ // // btnCreateStrongsDict // - this.btnCreateStrongsDict.Location = new System.Drawing.Point(324, 12); + this.btnCreateStrongsDict.Location = new System.Drawing.Point(432, 15); + this.btnCreateStrongsDict.Margin = new System.Windows.Forms.Padding(4); this.btnCreateStrongsDict.Name = "btnCreateStrongsDict"; - this.btnCreateStrongsDict.Size = new System.Drawing.Size(126, 23); + this.btnCreateStrongsDict.Size = new System.Drawing.Size(168, 28); this.btnCreateStrongsDict.TabIndex = 4; this.btnCreateStrongsDict.Text = "Create Strongs Dict"; this.btnCreateStrongsDict.UseVisualStyleBackColor = true; @@ -90,9 +98,10 @@ // // btnCreateRMAC // - this.btnCreateRMAC.Location = new System.Drawing.Point(456, 12); + this.btnCreateRMAC.Location = new System.Drawing.Point(608, 15); + this.btnCreateRMAC.Margin = new System.Windows.Forms.Padding(4); this.btnCreateRMAC.Name = "btnCreateRMAC"; - this.btnCreateRMAC.Size = new System.Drawing.Size(101, 23); + this.btnCreateRMAC.Size = new System.Drawing.Size(135, 28); this.btnCreateRMAC.TabIndex = 5; this.btnCreateRMAC.Text = "Create RMAC"; this.btnCreateRMAC.UseVisualStyleBackColor = true; @@ -100,19 +109,33 @@ // // btnRmacCrossRefs // - this.btnRmacCrossRefs.Location = new System.Drawing.Point(563, 12); + this.btnRmacCrossRefs.Location = new System.Drawing.Point(751, 15); + this.btnRmacCrossRefs.Margin = new System.Windows.Forms.Padding(4); this.btnRmacCrossRefs.Name = "btnRmacCrossRefs"; - this.btnRmacCrossRefs.Size = new System.Drawing.Size(149, 23); + this.btnRmacCrossRefs.Size = new System.Drawing.Size(199, 28); this.btnRmacCrossRefs.TabIndex = 6; this.btnRmacCrossRefs.Text = "Create RMAC Cross Refs"; this.btnRmacCrossRefs.UseVisualStyleBackColor = true; this.btnRmacCrossRefs.Click += new System.EventHandler(this.btnRmacCrossRefs_Click); // + // btnCreateGeolocationJson + // + this.btnCreateGeolocationJson.Location = new System.Drawing.Point(957, 15); + this.btnCreateGeolocationJson.Name = "btnCreateGeolocationJson"; + this.btnCreateGeolocationJson.Size = new System.Drawing.Size(207, 28); + this.btnCreateGeolocationJson.TabIndex = 7; + this.btnCreateGeolocationJson.Text = "Create Geolocation JSON"; + this.createGeolocationJsonTooltip.SetToolTip(this.btnCreateGeolocationJson, "Creates JSON files for geolocation data from https://www.openbible.info/geo/data/" + + "merged.txt"); + this.btnCreateGeolocationJson.UseVisualStyleBackColor = true; + this.btnCreateGeolocationJson.Click += new System.EventHandler(this.btnCreateGeolocationJson_Click); + // // frmMain // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(759, 561); + this.ClientSize = new System.Drawing.Size(1182, 690); + this.Controls.Add(this.btnCreateGeolocationJson); this.Controls.Add(this.btnRmacCrossRefs); this.Controls.Add(this.btnCreateRMAC); this.Controls.Add(this.btnCreateStrongsDict); @@ -120,6 +143,7 @@ this.Controls.Add(this.btnCreateText); this.Controls.Add(this.txtStatus); this.Controls.Add(this.btnCreateIndex); + this.Margin = new System.Windows.Forms.Padding(4); this.Name = "frmMain"; this.Text = "Dynamic Bible Utility"; this.ResumeLayout(false); @@ -136,6 +160,8 @@ private System.Windows.Forms.Button btnCreateStrongsDict; private System.Windows.Forms.Button btnCreateRMAC; private System.Windows.Forms.Button btnRmacCrossRefs; + private System.Windows.Forms.Button btnCreateGeolocationJson; + private System.Windows.Forms.ToolTip createGeolocationJsonTooltip; } } diff --git a/DynamicBibleUtility/DynamicBibleUtility/frmMain.cs b/DynamicBibleUtility/DynamicBibleUtility/frmMain.cs index e9c6e19a..13fddc07 100644 --- a/DynamicBibleUtility/DynamicBibleUtility/frmMain.cs +++ b/DynamicBibleUtility/DynamicBibleUtility/frmMain.cs @@ -1,4 +1,5 @@ using DynamicBible.Schemas; +using DynamicBibleUtility.Geolocation; using JMW.Extensions.String; using System; using System.Collections.Generic; @@ -647,6 +648,78 @@ namespace DynamicBibleUtility } } + #endregion RMAC + + #region Geolocation JSON + + /// + /// Handles creating geolocation JSON files when the appropriate button is clicked. + /// + /// Sender of the event; ignored. + /// Event arguments; ignored. + private void btnCreateGeolocationJson_Click(object sender, EventArgs eventArguments) + { + var _thread = new Thread(CreateGeolocationJson); + _thread.SetApartmentState(ApartmentState.STA); + _thread.IsBackground = true; + _thread.Start(); + } + + /// + /// Prompts the user for a geolocation data file () + /// and converts a chosen file into the appropriate output JSON files for the Dynamic Bible app. + /// + private void CreateGeolocationJson() + { + // LET THE USER CHOOSE THE FILE WITH GEOLOCATION DATA. + OpenFileDialog open_file_dialog = new OpenFileDialog(); + DialogResult file_dialog_result = open_file_dialog.ShowDialog(); + bool file_chosen = (file_dialog_result == DialogResult.OK); + if (!file_chosen) + { + // The user chose not to create a file. + return; + } + + try + { + // READ THE LOCATION INFORMATION FROM THE FILE. + IEnumerable locations = OpenBibleDotInfoLocationParser.Parse(open_file_dialog.FileName); + UpdateStatus($"Parsed {locations.Count()} locations.\n"); + + // CREATE MORE USEFUL INDICES FOR THE LOCATIONS. + // Indexing by name and verse references is useful for quick lookups + // in the Dynamic Bible app. + BibleLocationIndexByName locations_by_name = new BibleLocationIndexByName(locations); + UpdateStatus($"Finished indexing locations by name.\n"); + BibleLocationIndexByVerse locations_by_verse = new BibleLocationIndexByVerse(locations); + UpdateStatus($"Finished indexing locations by verse.\n"); + + // WRITE OUT THE GEOLOCATION DATA TO JSON FORMAT. + const string LOCATIONS_BY_NAME_JSON_FILENAME = "locations_by_name.json"; + string locations_by_name_in_json = JSON.ToJSON(locations_by_name.NameToLocationLookup); + File.WriteAllText(LOCATIONS_BY_NAME_JSON_FILENAME, locations_by_name_in_json); + UpdateStatus($"Wrote locations by name to {LOCATIONS_BY_NAME_JSON_FILENAME} in current working directory.\n"); + + const string LOCATIONS_BY_VERSE_JSON_FILENAME = "locations_by_verse.json"; + string locations_by_verse_in_json = JSON.ToJSON(locations_by_verse.VerseToLocationNameLookup); + File.WriteAllText(LOCATIONS_BY_VERSE_JSON_FILENAME, locations_by_verse_in_json); + UpdateStatus($"Wrote locations by verse to {LOCATIONS_BY_VERSE_JSON_FILENAME} in current working directory.\n"); + + /// TODO(Jacob): Add Strong's numbers to the location data (with JavaScript property name of 'sn') + /// and create a lookup based on Strong's numbers. This would be more useful in the context of + /// the Dynamic Bible app. + + // INFORM THE USER THAT CREATING THE GEOLOCATION JSON FILES IS COMPLETE. + UpdateStatus("Done.\n"); + } + catch (Exception exception) + { + UpdateStatus($"Exception while processing geolocations: {exception}\n"); + } + } + + #endregion } } \ No newline at end of file diff --git a/DynamicBibleUtility/DynamicBibleUtility/frmMain.resx b/DynamicBibleUtility/DynamicBibleUtility/frmMain.resx index 29dcb1b3..2d953a41 100644 --- a/DynamicBibleUtility/DynamicBibleUtility/frmMain.resx +++ b/DynamicBibleUtility/DynamicBibleUtility/frmMain.resx @@ -117,4 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 17, 17 + \ No newline at end of file