DynamicBibleUtility - Adding Strong's numbers to geolocation data.

This commit is contained in:
Jacob Pike 2017-04-11 17:45:37 -04:00
parent b67e52950a
commit 274cecb598
8 changed files with 1482 additions and 29 deletions

View File

@ -65,9 +65,11 @@
</Compile>
<Compile Include="Geolocation\BibleBook.cs" />
<Compile Include="Geolocation\BibleLocationIndexByName.cs" />
<Compile Include="Geolocation\BibleLocationIndexByStrongsNumbers.cs" />
<Compile Include="Geolocation\BibleLocationIndexByVerse.cs" />
<Compile Include="Geolocation\BibleLocationReference.cs" />
<Compile Include="Geolocation\BibleVerseReference.cs" />
<Compile Include="Geolocation\LocationNameToStrongsNumberLookup.cs" />
<Compile Include="Geolocation\OpenBibleDotInfoLocationParser.cs" />
<Compile Include="Helpers.cs" />
<Compile Include="Index.cs" />

View File

@ -7,8 +7,8 @@ namespace DynamicBibleUtility.Geolocation
/// </summary>
public class BibleLocationIndexByName
{
/// <summary>A mapping of location names to full location data.</summary>
public IDictionary<string, BibleLocationReference> NameToLocationLookup = new Dictionary<string, BibleLocationReference>();
/// <summary>A mapping of location names to full location data (in the JSON format).</summary>
public IDictionary<string, dynamic> NameToLocationLookup = new Dictionary<string, dynamic>();
/// <summary>
/// Creates the index from the provided locations.
@ -24,7 +24,20 @@ namespace DynamicBibleUtility.Geolocation
// The information currently isn't useful without these coordinates.
if (location.HasGeographicCoordinates)
{
NameToLocationLookup[location.Name] = location;
// Since the location data needs to be converted to JSON in different
// scenarios with different properties serialized, there's not an easy
// way to simply mark which fields should/shouldn't be serialized in
// all situations. While a custom JSON converter could be used,
// creating a dynamic object here seemed simpler. In this scenario,
// the name doesn't need to be included in the member data since
// it already serves as the key in the lookup.
NameToLocationLookup[location.Name] = new
{
sn = location.StrongsNumbers,
lat = location.Latitude,
lon = location.Longitude,
vss = location.VerseReferenceStrings
};
}
}
}

View File

@ -0,0 +1,89 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
namespace DynamicBibleUtility.Geolocation
{
/// <summary>
/// An index of Bible location data by Strong's numbers.
///
/// Strong's numbers tend to be more useful for lookups in the Dynamic Bible app
/// due to how they're integrated into the rest of the app and can help more clearly
/// define a particular location.
///
/// This class maintains a simple lookup of Strong's numbers to location names
/// in addition to a lookup that includes the full location data.
/// </summary>
public class BibleLocationIndexByStrongsNumbers
{
/// <summary>
/// A mapping of Strong's numbers to normalized location names.
/// A Strong's number may refer to multiple locations.
/// </summary>
public IDictionary<string, List<string>> StrongsNumberToLocationNameLookup = new Dictionary<string, List<string>>();
/// <summary>
/// A mapping of Strong's numbers to full location data (in JSON format).
/// A Strong's number may refer to multiple locations.
/// </summary>
public IDictionary<string, List<dynamic>> StrongsNumberToLocationLookup = new Dictionary<string, List<dynamic>>();
/// <summary>
/// Creates the index from the provided locations.
/// </summary>
/// <param name="locations">The locations to put in the index.</param>
/// <exception cref="System.NullReferenceException">Thrown if the locations are null.</exception>
public BibleLocationIndexByStrongsNumbers(IEnumerable<BibleLocationReference> locations)
{
// INDEX THE LOCATIONS BY STRONG'S NUMBERS.
foreach (var location in locations)
{
// DON'T INDEX THE LOCATION IF IT HAS GEOGRAPHIC COORDINATES.
// The information currently isn't useful without these coordinates.
if (!location.HasGeographicCoordinates)
{
continue;
}
// DON'T INDEX THE LOCATION IF IT DOESN'T HAVE ANY STRONG'S NUMBERS.
bool location_has_strongs_numbers = location.StrongsNumbers.Any();
if (!location_has_strongs_numbers)
{
continue;
}
// INDEX THE LOCATION BY STRONG'S NUMBERS.
foreach (string strongs_number in location.StrongsNumbers)
{
// MAKE SURE EXISTING COLLECTIONS EXIST FOR THE STRONG'S NUMBER.
bool strongs_number_exists_in_index = StrongsNumberToLocationLookup.ContainsKey(strongs_number);
if (!strongs_number_exists_in_index)
{
StrongsNumberToLocationNameLookup[strongs_number] = new List<string>();
StrongsNumberToLocationLookup[strongs_number] = new List<dynamic>();
}
// CONVERT THE LOCATION TO JSON FORMAT.
// Since the location data needs to be converted to JSON in different
// scenarios with different properties serialized, there's not an easy
// way to simply mark which fields should/shouldn't be serialized in
// all situations. While a custom JSON converter could be used,
// creating a dynamic object here seemed simpler. In this scenario,
// the Strong's number doesn't need to be included in the member data
// since it already serves as the key in the lookup.
dynamic converted_location = new
{
name = location.Name,
lat = location.Latitude,
lon = location.Longitude,
vss = location.VerseReferenceStrings
};
// INDEX THE LOCATION BY STRONG'S NUMBER.
// Indices with just the location name and full location data are maintained.
StrongsNumberToLocationNameLookup[strongs_number].Add(location.Name);
StrongsNumberToLocationLookup[strongs_number].Add(converted_location);
}
}
}
}
}

View File

@ -6,35 +6,27 @@ namespace DynamicBibleUtility.Geolocation
{
/// <summary>
/// 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.
/// </summary>
public class BibleLocationReference
{
/// <summary>
/// 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.
/// </summary>
[JsonIgnore]
/// <summary>The name of the location.</summary>
public string Name = "";
/// <summary>
/// Any Strong's numbers for the location name, if available.
/// Includes an 'H' prefix for Hebrew and a 'G' prefix for Greek before the actual numbers.
/// </summary>
public IEnumerable<string> StrongsNumbers = new List<string>();
/// <summary>The latitude of the location, if available.</summary>
[JsonProperty("lat")]
public double? Latitude = null;
/// <summary>The longitude of the location, if available.</summary>
[JsonProperty("lon")]
public double? Longitude = null;
/// <summary>References to verses that mention the location.</summary>
[JsonIgnore]
public IEnumerable<BibleVerseReference> VerseReferences = new List<BibleVerseReference>();
/// <summary>
/// Gets references to verses that mention the location, in their short string form
/// as used by the Dynamic Bible app.
/// </summary>
[JsonProperty("vss")]
public IEnumerable<string> VerseReferenceStrings
{
get
@ -46,9 +38,7 @@ namespace DynamicBibleUtility.Geolocation
/// <summary>
/// True if this location information has full geographic coordinates; false otherwise.
/// Marked such that it doesn't get serialized to JSON.
/// </summary>
[JsonIgnore]
public bool HasGeographicCoordinates
{
get

View File

@ -1,5 +1,4 @@
using System;
using Newtonsoft.Json;
namespace DynamicBibleUtility.Geolocation
{

View File

@ -21,6 +21,8 @@ namespace DynamicBibleUtility.Geolocation
/// Creative Commons Attribution license (see
/// https://www.openbible.info/geo/ and
/// https://creativecommons.org/licenses/by/4.0/).
///
/// Strong's numbers are added using <see cref="LocationNameToStrongsNumberLookup"/>.
/// </summary>
public class OpenBibleDotInfoLocationParser
{
@ -32,6 +34,8 @@ namespace DynamicBibleUtility.Geolocation
/// <exception cref="Exception">Thrown if a parsing error occurs.</exception>
public static IEnumerable<BibleLocationReference> Parse(string filepath)
{
List<string> locations_without_normalized_names = new List<string>();
// READ THE ENTIRE GEOLOCATION DATA FILE.
// It is small enough to store completely in memory.
string[] geolocation_input_file_lines = File.ReadAllLines(filepath);
@ -81,6 +85,9 @@ namespace DynamicBibleUtility.Geolocation
string verse_references_csv_list = current_line_fields[VERSE_REFERENCES_INDEX];
location.VerseReferences = ParseVerseReferences(verse_references_csv_list);
// ADD STRONG'S NUMBERS REFERENCES TO THE LOCATION.
location.StrongsNumbers = LocationNameToStrongsNumberLookup.GetStrongsNumbers(location.Name);
// ADD THE LOCATION INFORMATION FOR RETURNING.
locations.Add(location);
}

View File

@ -689,12 +689,13 @@ namespace DynamicBibleUtility
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.
// Indexing in these different ways 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");
BibleLocationIndexByStrongsNumbers locations_by_strongs_numbers = new BibleLocationIndexByStrongsNumbers(locations);
UpdateStatus($"Finished indexing locations by Strong's numbers.\n");
// WRITE OUT THE GEOLOCATION DATA TO JSON FORMAT.
const string LOCATIONS_BY_NAME_JSON_FILENAME = "locations_by_name.json";
@ -702,14 +703,20 @@ namespace DynamicBibleUtility
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");
const string LOCATION_NAMES_BY_VERSE_JSON_FILENAME = "location_names_by_verse.json";
string location_names_by_verse_in_json = JSON.ToJSON(locations_by_verse.VerseToLocationNameLookup);
File.WriteAllText(LOCATION_NAMES_BY_VERSE_JSON_FILENAME, location_names_by_verse_in_json);
UpdateStatus($"Wrote location names by verse to {LOCATION_NAMES_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.
const string LOCATION_NAMES_BY_STRONGS_NUMBER = "location_names_by_strongs.json";
string location_names_by_strongs_number_in_json = JSON.ToJSON(locations_by_strongs_numbers.StrongsNumberToLocationNameLookup);
File.WriteAllText(LOCATION_NAMES_BY_STRONGS_NUMBER, location_names_by_strongs_number_in_json);
UpdateStatus($"Wrote location names by Strong's number to {LOCATION_NAMES_BY_STRONGS_NUMBER} in current working directory.\n");
const string LOCATIONS_BY_STRONGS_NUMBER = "locations_by_strongs.json";
string locations_by_strongs_number_in_json = JSON.ToJSON(locations_by_strongs_numbers.StrongsNumberToLocationLookup);
File.WriteAllText(LOCATIONS_BY_STRONGS_NUMBER, locations_by_strongs_number_in_json);
UpdateStatus($"Wrote locations by Strong's number to {LOCATIONS_BY_STRONGS_NUMBER} in current working directory.\n");
// INFORM THE USER THAT CREATING THE GEOLOCATION JSON FILES IS COMPLETE.
UpdateStatus("Done.\n");