internal WorldCollection GetWorlds(ResourceManager resourceManager, bool cacheResults = true) { lock (this) { // Have it cached - just return it if (worlds != null) { return(worlds); } // Can't look it up; failure case if (DataFile == null) { return(null); } // Otherwise, look it up WorldCollection data = resourceManager.GetDeserializableFileObject(@"~/res/Sectors/" + DataFile, typeof(WorldCollection), cacheResults: false, mediaType: DataFile.Type) as WorldCollection; foreach (World world in data) { world.Sector = this; } if (cacheResults) { worlds = data; } return(data); } }
internal override WorldCollection GetWorlds(ResourceManager resourceManager, bool cacheResults = true) { if (this.worlds != null) { return(this.worlds); } WorldCollection worlds = basis.GetWorlds(resourceManager, cacheResults); if (worlds == null) { return(null); } WorldCollection dots = new WorldCollection(); foreach (var world in worlds) { var dot = new World(); dot.Hex = world.Hex; dot.UWP = "???????-?"; dot.PBG = "???"; dot.Allegiance = "??"; dot.Sector = this; dots[dot.X, dot.Y] = dot; } if (cacheResults) { this.worlds = dots; } return(dots); }
public Sector(Stream stream, string mediaType) { WorldCollection wc = new WorldCollection(); wc.Deserialize(stream, mediaType); foreach (World world in wc) { world.Sector = this; } m_data = wc; }
internal Sector(Stream stream, string mediaType, ErrorLogger?errors) : this() { WorldCollection wc = new WorldCollection(isUserData: true); wc.Deserialize(stream, mediaType, errors); foreach (World world in wc) { world.Sector = this; } worlds = wc; }
public WorldCollection MakeDotmap() { WorldCollection dots = new WorldCollection(); foreach (var world in this) { var dot = new World() { Hex = world.Hex, UWP = "???????-?", PBG = "???", Allegiance = "??", Sector = null }; dots[dot.X, dot.Y] = dot; } return(dots); }
public void Resolve(SectorMap sectorMap, ResourceManager resourceManager, out Sector sector, out World world) { if (sectorMap == null) { throw new ArgumentNullException("sectorMap"); } sector = null; world = null; sector = sectorMap.FromLocation(Sector.X, Sector.Y); if (sector != null) { WorldCollection worlds = sector.GetWorlds(resourceManager, cacheResults: true); if (worlds != null) { world = worlds[World.X, World.Y]; } } }
public void Resolve(SectorMap.Milieu sectorMap, ResourceManager resourceManager, out Sector sector, out World world) { if (sectorMap == null) { throw new ArgumentNullException(nameof(sectorMap)); } sector = null; world = null; sector = sectorMap.FromLocation(Sector.X, Sector.Y); if (sector == null) { return; } WorldCollection worlds = sector.GetWorlds(resourceManager, cacheResults: true); if (worlds != null) { world = worlds[Hex]; } }
internal void Serialize(ResourceManager resourceManager, TextWriter writer, string mediaType, bool includeMetadata = true, bool includeHeader = true, bool sscoords = false, WorldFilter filter = null) { WorldCollection worlds = GetWorlds(resourceManager); // TODO: less hacky T5 support bool isT5 = (mediaType == "TabDelimited" || mediaType == "SecondSurvey"); if (mediaType == "TabDelimited") { if (worlds != null) { worlds.Serialize(writer, mediaType, includeHeader: includeHeader, filter: filter); } return; } if (includeMetadata) { // Header // writer.WriteLine("# Generated by http://travellermap.com"); writer.WriteLine("# " + DateTime.Now.ToString("yyyy-MM-ddTHH:mm:sszzz", DateTimeFormatInfo.InvariantInfo)); writer.WriteLine(); writer.WriteLine("# {0}", Names[0]); writer.WriteLine("# {0},{1}", X, Y); writer.WriteLine(); foreach (var name in Names) { if (name.Lang != null) { writer.WriteLine("# Name: {0} ({1})", name.Text, name.Lang); } else { writer.WriteLine("# Name: {0}", name); } } if (Credits != null) { string stripped = Regex.Replace(Credits, "<.*?>", ""); stripped = Regex.Replace(stripped, @"\s+", " "); stripped = stripped.Trim(); writer.WriteLine(); writer.WriteLine("# Credits: {0}", stripped); } if (DataFile != null) { writer.WriteLine(); if (DataFile.Milieu != null) { writer.WriteLine("# Milieu: {0}", DataFile.Milieu); } writer.WriteLine(); if (DataFile.Author != null) { writer.WriteLine("# Author: {0}", DataFile.Author); } if (DataFile.Publisher != null) { writer.WriteLine("# Publisher: {0}", DataFile.Publisher); } if (DataFile.Copyright != null) { writer.WriteLine("# Copyright: {0}", DataFile.Copyright); } if (DataFile.Source != null) { writer.WriteLine("# Source: {0}", DataFile.Source); } if (DataFile.Ref != null) { writer.WriteLine("# Ref: {0}", DataFile.Ref); } } writer.WriteLine(); for (int i = 0; i < 16; ++i) { char c = (char)('A' + i); Subsector ss = Subsector(c); writer.WriteLine("# Subsector {0}: {1}", c, ss?.Name ?? ""); } writer.WriteLine(); } if (worlds == null) { if (includeMetadata) { writer.WriteLine("# No world data available"); } return; } // Allegiances if (includeMetadata) { // Use codes as present in the data, to match the worlds foreach (string code in worlds.AllegianceCodes().OrderBy(s => s)) { var alleg = GetAllegianceFromCode(code); if (alleg != null) { writer.WriteLine("# Alleg: {0}: \"{1}\"", isT5 ? code : SecondSurvey.T5AllegianceCodeToLegacyCode(code), alleg.Name); } } writer.WriteLine(); } // Worlds worlds.Serialize(writer, mediaType, includeHeader: includeHeader, sscoords: sscoords, filter: filter); }
public static void PopulateDatabase(ResourceManager resourceManager, StatusCallback callback) { // Lock on this class rather than the cacheResults. Tile requests are not // blocked but we don't index twice. lock (typeof(SearchEngine)) { // NOTE: This (re)initializes a static data structure used for // resolving names into sector locations, so needs to be run // before any other objects (e.g. Worlds) are loaded. SectorMap map = SectorMap.FromName(SectorMap.DefaultSetting, resourceManager); using (var connection = DBUtil.MakeConnection()) { SqlCommand sqlCommand; // // Repopulate the tables - locally first // FUTURE: will need to batch this up rather than keep it in memory! // DataTable dt_sectors = new DataTable(); for (int i = 0; i < 3; ++i) { dt_sectors.Columns.Add(new DataColumn()); } DataTable dt_subsectors = new DataTable(); for (int i = 0; i < 4; ++i) { dt_subsectors.Columns.Add(new DataColumn()); } DataTable dt_worlds = new DataTable(); for (int i = 0; i < 6; ++i) { dt_worlds.Columns.Add(new DataColumn()); } callback("Parsing data..."); foreach (Sector sector in map.Sectors) { if (!sector.Tags.Contains("OTU")) { continue; } foreach (Name name in sector.Names) { DataRow row = dt_sectors.NewRow(); row.ItemArray = new object[] { sector.X, sector.Y, name.Text }; dt_sectors.Rows.Add(row); } foreach (Subsector subsector in sector.Subsectors) { DataRow row = dt_subsectors.NewRow(); row.ItemArray = new object[] { sector.X, sector.Y, subsector.Index, subsector.Name }; dt_subsectors.Rows.Add(row); } #if DEBUG if (!sector.Selected) { continue; } #endif // NOTE: May need to page this at some point WorldCollection worlds = sector.GetWorlds(resourceManager, cacheResults: false); if (worlds == null) { continue; } foreach (World world in worlds) { DataRow row = dt_worlds.NewRow(); row.ItemArray = new object[] { sector.X, sector.Y, world.X, world.Y, world.Name != null && world.Name.Length > 0 ? (object)world.Name : (object)DBNull.Value, world.UWP }; dt_worlds.Rows.Add(row); } } // // Rebuild the tables with fresh schema // string[] rebuild_schema = { "IF EXISTS(SELECT 1 FROM sys.objects WHERE OBJECT_ID = OBJECT_ID(N'sectors') AND type = (N'U')) DROP TABLE sectors", "CREATE TABLE sectors(x int NOT NULL,y int NOT NULL,name nvarchar(50) NULL)", "CREATE NONCLUSTERED INDEX sector_name ON sectors ( name ASC ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)", "IF EXISTS(SELECT 1 FROM sys.objects WHERE OBJECT_ID = OBJECT_ID(N'subsectors') AND type = (N'U')) DROP TABLE subsectors", "CREATE TABLE subsectors (sector_x int NOT NULL, sector_y int NOT NULL, subsector_index char(1) NOT NULL, name nvarchar(50) NULL )", "CREATE NONCLUSTERED INDEX subsector_name ON subsectors ( name ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)", "IF EXISTS(SELECT 1 FROM sys.objects WHERE OBJECT_ID = OBJECT_ID(N'worlds') AND type = (N'U')) DROP TABLE worlds", "CREATE TABLE worlds( sector_x int NOT NULL, sector_y int NOT NULL, hex_x int NOT NULL, hex_y int NOT NULL, name nvarchar(50) NULL, uwp nchar(9) NULL )", "CREATE NONCLUSTERED INDEX world_name ON worlds ( name ASC ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)", "CREATE NONCLUSTERED INDEX world_uwp ON worlds ( uwp ASC ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)", }; callback("Rebuilding schema..."); foreach (string cmd in rebuild_schema) { sqlCommand = new SqlCommand(cmd, connection); sqlCommand.ExecuteNonQuery(); } // // And shovel the data into the database en masse // using (var bulk = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, null)) { callback(String.Format("Writing {0} sectors...", dt_sectors.Rows.Count)); bulk.BatchSize = dt_sectors.Rows.Count; bulk.DestinationTableName = "sectors"; bulk.WriteToServer(dt_sectors); bulk.Close(); } using (var bulk = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, null)) { callback(String.Format("Writing {0} subsectors...", dt_subsectors.Rows.Count)); bulk.BatchSize = dt_subsectors.Rows.Count; bulk.DestinationTableName = "subsectors"; bulk.WriteToServer(dt_subsectors); bulk.Close(); } using (var bulk = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, null)) { callback(String.Format("Writing {0} worlds...", dt_worlds.Rows.Count)); bulk.BatchSize = 4096; bulk.DestinationTableName = "worlds"; bulk.WriteToServer(dt_worlds); bulk.Close(); } } callback("Complete!"); } }
internal void Serialize(ResourceManager resourceManager, TextWriter writer, string mediaType, SectorSerializeOptions options) { WorldCollection worlds = GetWorlds(resourceManager); // TODO: less hacky T5 support bool isT5 = (mediaType == "TabDelimited" || mediaType == "SecondSurvey"); if (mediaType == "TabDelimited") { worlds?.Serialize(writer, mediaType, options); return; } if (options.includeMetadata) { // Header // writer.WriteLine("# Generated by https://travellermap.com"); writer.WriteLine("# " + DateTime.Now.ToString("yyyy-MM-ddTHH:mm:sszzz", DateTimeFormatInfo.InvariantInfo)); writer.WriteLine(); writer.WriteLine($"# {Names[0]}"); writer.WriteLine($"# {X},{Y}"); writer.WriteLine(); foreach (var name in Names) { if (name.Lang != null) { writer.WriteLine($"# Name: {name.Text} ({name.Lang})"); } else { writer.WriteLine($"# Name: {name}"); } } writer.WriteLine(); writer.WriteLine($"# Milieu: {CanonicalMilieu}"); if (Credits != null) { string stripped = Regex.Replace(Credits, "<.*?>", ""); stripped = Regex.Replace(stripped, @"\s+", " "); stripped = stripped.Trim(); writer.WriteLine(); writer.WriteLine($"# Credits: {stripped}"); } if (DataFile != null) { writer.WriteLine(); if (DataFile.Author != null) { writer.WriteLine($"# Author: {DataFile.Author}"); } if (DataFile.Publisher != null) { writer.WriteLine($"# Publisher: {DataFile.Publisher}"); } if (DataFile.Copyright != null) { writer.WriteLine($"# Copyright: {DataFile.Copyright}"); } if (DataFile.Source != null) { writer.WriteLine($"# Source: {DataFile.Source}"); } if (DataFile.Ref != null) { writer.WriteLine($"# Ref: {DataFile.Ref}"); } } writer.WriteLine(); for (int i = 0; i < 16; ++i) { char c = (char)('A' + i); Subsector ss = Subsector(c); writer.WriteLine($"# Subsector {c}: {ss?.Name ?? ""}"); } writer.WriteLine(); } if (worlds == null) { if (options.includeMetadata) { writer.WriteLine("# No world data available"); } return; } // Allegiances if (options.includeMetadata) { // Use codes as present in the data, to match the worlds foreach (string code in worlds.AllegianceCodes().OrderBy(s => s)) { var alleg = GetAllegianceFromCode(code); if (alleg != null) { var a = isT5 ? code : SecondSurvey.T5AllegianceCodeToLegacyCode(code); writer.WriteLine($"# Alleg: {a}: \"{alleg.Name}\""); } } writer.WriteLine(); } // Worlds worlds.Serialize(writer, mediaType, options); }
public static void PopulateDatabase(ResourceManager resourceManager, StatusCallback callback) { // Lock to prevent indexing twice, without blocking tile requests. lock (SearchEngine.s_lock) { // NOTE: This (re)initializes a static data structure used for // resolving names into sector locations, so needs to be run // before any other objects (e.g. Worlds) are loaded. SectorMap map = SectorMap.GetInstance(resourceManager); using (var connection = DBUtil.MakeConnection()) { SqlCommand sqlCommand; // // Repopulate the tables - locally first // FUTURE: will need to batch this up rather than keep it in memory! // DataTable dt_sectors = new DataTable(); for (int i = 0; i < SECTORS_COLUMNS.Length; ++i) { dt_sectors.Columns.Add(new DataColumn()); } DataTable dt_subsectors = new DataTable(); for (int i = 0; i < SUBSECTORS_COLUMNS.Length; ++i) { dt_subsectors.Columns.Add(new DataColumn()); } DataTable dt_worlds = new DataTable(); for (int i = 0; i < WORLDS_COLUMNS.Length; ++i) { dt_worlds.Columns.Add(new DataColumn()); } DataTable dt_labels = new DataTable(); for (int i = 0; i < LABELS_COLUMNS.Length; ++i) { dt_labels.Columns.Add(new DataColumn()); } // Map of (milieu, string) => [ points ... ] Dictionary <Tuple <string, string>, List <Point> > labels = new Dictionary <Tuple <string, string>, List <Point> >(); Action <string, string, Point> AddLabel = (string milieu, string text, Point coords) => { if (text == null) { return; } text = SanifyLabel(text); var key = Tuple.Create(milieu, text); if (!labels.ContainsKey(key)) { labels.Add(key, new List <Point>()); } labels[key].Add(coords); }; callback("Parsing data..."); foreach (Sector sector in map.Sectors) { // TODO: Index alternate milieu if (!sector.Tags.Contains("OTU") && !sector.Tags.Contains("Faraway")) { continue; } foreach (Name name in sector.Names) { DataRow row = dt_sectors.NewRow(); row.ItemArray = new object[] { sector.CanonicalMilieu, sector.X, sector.Y, name.Text }; dt_sectors.Rows.Add(row); } if (!string.IsNullOrEmpty(sector.Abbreviation)) { DataRow row = dt_sectors.NewRow(); row.ItemArray = new object[] { sector.CanonicalMilieu, sector.X, sector.Y, sector.Abbreviation }; dt_sectors.Rows.Add(row); } foreach (Subsector subsector in sector.Subsectors) { DataRow row = dt_subsectors.NewRow(); row.ItemArray = new object[] { sector.CanonicalMilieu, sector.X, sector.Y, subsector.Index, subsector.Name }; dt_subsectors.Rows.Add(row); } foreach (Border border in sector.Borders.Where(b => b.ShowLabel)) { AddLabel( sector.CanonicalMilieu, border.GetLabel(sector), Astrometrics.LocationToCoordinates(new Location(sector.Location, border.LabelPosition))); } foreach (Label label in sector.Labels) { AddLabel( sector.CanonicalMilieu, label.Text, Astrometrics.LocationToCoordinates(new Location(sector.Location, label.Hex))); } #if DEBUG if (!sector.Selected) { continue; } #endif // NOTE: May need to page this at some point WorldCollection worlds = sector.GetWorlds(resourceManager, cacheResults: false); if (worlds == null) { continue; } var world_query = from world in worlds where !world.IsPlaceholder select world; foreach (World world in world_query) { DataRow row = dt_worlds.NewRow(); row.ItemArray = new object[] { sector.CanonicalMilieu, world.Coordinates.X, world.Coordinates.Y, sector.X, sector.Y, world.X, world.Y, string.IsNullOrEmpty(world.Name) ? (object)DBNull.Value : (object)world.Name, world.UWP, world.Remarks, world.PBG, string.IsNullOrEmpty(world.Zone) ? "G" : world.Zone, world.Allegiance, sector.Names.Count > 0 ? (object)sector.Names[0] : (object)DBNull.Value }; dt_worlds.Rows.Add(row); } } foreach (KeyValuePair <Tuple <string, string>, List <Point> > entry in labels) { string milieu = entry.Key.Item1; string name = entry.Key.Item2; List <Point> points = entry.Value; Point avg = new Point( (int)Math.Round(points.Select(p => p.X).Average()), (int)Math.Round(points.Select(p => p.Y).Average())); Point min = new Point(points.Select(p => p.X).Min(), points.Select(p => p.Y).Min()); Point max = new Point(points.Select(p => p.X).Max(), points.Select(p => p.Y).Max()); Size size = new Size(max.X - min.X, max.Y - min.Y); int radius = Math.Max(size.Width, size.Height); DataRow row = dt_labels.NewRow(); row.ItemArray = new object[] { milieu, avg.X, avg.Y, radius, name }; dt_labels.Rows.Add(row); } // // Rebuild the tables with fresh schema // const string INDEX_OPTIONS = " WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)"; const string DROP_TABLE_IF_EXISTS = "IF EXISTS(SELECT 1 FROM sys.objects WHERE OBJECT_ID = OBJECT_ID(N'{0}') AND type = (N'U')) DROP TABLE {0}"; string[] rebuild_schema = { string.Format(DROP_TABLE_IF_EXISTS, "sectors"), "CREATE TABLE sectors (" + string.Join(",", SECTORS_COLUMNS) + ")", "CREATE NONCLUSTERED INDEX sector_name ON sectors ( name ASC )" + INDEX_OPTIONS, "CREATE NONCLUSTERED INDEX sector_milieu ON sectors ( milieu ASC )" + INDEX_OPTIONS, string.Format(DROP_TABLE_IF_EXISTS, "subsectors"), "CREATE TABLE subsectors (" + string.Join(",", SUBSECTORS_COLUMNS) + ")", "CREATE NONCLUSTERED INDEX subsector_name ON subsectors ( name ASC )" + INDEX_OPTIONS, "CREATE NONCLUSTERED INDEX subsector_milieu ON subsectors ( milieu ASC )" + INDEX_OPTIONS, string.Format(DROP_TABLE_IF_EXISTS, "worlds"), "CREATE TABLE worlds (" + string.Join(",", WORLDS_COLUMNS) + ")", "CREATE NONCLUSTERED INDEX world_name ON worlds ( name ASC )" + INDEX_OPTIONS, "CREATE NONCLUSTERED INDEX world_uwp ON worlds ( uwp ASC )" + INDEX_OPTIONS, "CREATE NONCLUSTERED INDEX world_pbg ON worlds ( pbg ASC )" + INDEX_OPTIONS, "CREATE NONCLUSTERED INDEX world_alleg ON worlds ( alleg ASC )" + INDEX_OPTIONS, "CREATE NONCLUSTERED INDEX world_sector_name ON worlds ( sector_name ASC )" + INDEX_OPTIONS, "CREATE NONCLUSTERED INDEX world_milieu ON worlds ( milieu ASC )" + INDEX_OPTIONS, string.Format(DROP_TABLE_IF_EXISTS, "labels"), "CREATE TABLE labels (" + string.Join(",", LABELS_COLUMNS) + ")", "CREATE NONCLUSTERED INDEX name ON labels ( name ASC )" + INDEX_OPTIONS, "CREATE NONCLUSTERED INDEX milieu ON labels ( milieu ASC )" + INDEX_OPTIONS, }; callback("Rebuilding schema..."); foreach (string cmd in rebuild_schema) { sqlCommand = new SqlCommand(cmd, connection); sqlCommand.ExecuteNonQuery(); } // // And shovel the data into the database en masse // Action <string, DataTable, int> BulkInsert = (string name, DataTable table, int batchSize) => { using (var bulk = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, null)) { callback($"Writing {table.Rows.Count} {name}..."); bulk.BatchSize = batchSize; bulk.DestinationTableName = name; bulk.WriteToServer(table); } }; BulkInsert("sectors", dt_sectors, dt_sectors.Rows.Count); BulkInsert("subsectors", dt_subsectors, dt_subsectors.Rows.Count); BulkInsert("worlds", dt_worlds, 4096); BulkInsert("labels", dt_labels, dt_labels.Rows.Count); } callback("Complete!"); } }
public void Serialize(ResourceManager resourceManager, TextWriter writer, string mediaType, bool includeMetadata = true, bool includeHeader = true, WorldFilter filter = null) { WorldCollection worlds = GetWorlds(resourceManager); // TODO: less hacky T5 support if (mediaType == "TabDelimited") { if (worlds != null) { worlds.Serialize(writer, mediaType, includeHeader, filter); } return; } if (includeMetadata) { // Header // writer.WriteLine("# Generated by http://www.travellermap.com"); writer.WriteLine("# " + DateTime.Now.ToString("yyyy-MM-ddTHH:mm:sszzz", DateTimeFormatInfo.InvariantInfo)); writer.WriteLine(); writer.WriteLine("# {0}", this.Names[0]); writer.WriteLine("# {0},{1}", this.X, this.Y); writer.WriteLine(); foreach (var name in Names) { if (name.Lang != null) { writer.WriteLine("# Name: {0} ({1})", name.Text, name.Lang); } else { writer.WriteLine("# Name: {0}", name); } } if (Credits != null) { string stripped = Regex.Replace(Credits, "<.*?>", ""); stripped = Regex.Replace(stripped, @"\s+", " "); stripped = stripped.Trim(); writer.WriteLine(); writer.WriteLine("# Credits: {0}", stripped); } if (DataFile != null) { writer.WriteLine(); if (DataFile.Era != null) { writer.WriteLine("# Era: {0}", DataFile.Era); } writer.WriteLine(); if (DataFile.Author != null) { writer.WriteLine("# Author: {0}", DataFile.Author); } if (DataFile.Publisher != null) { writer.WriteLine("# Publisher: {0}", DataFile.Publisher); } if (DataFile.Copyright != null) { writer.WriteLine("# Copyright: {0}", DataFile.Copyright); } if (DataFile.Source != null) { writer.WriteLine("# Source: {0}", DataFile.Source); } if (DataFile.Ref != null) { writer.WriteLine("# Ref: {0}", DataFile.Ref); } } writer.WriteLine(); for (int i = 0; i < 16; ++i) { char c = (char)('A' + i); Subsector ss = this[c]; writer.WriteLine("# Subsector {0}: {1}", c, (ss != null ? ss.Name : "")); } writer.WriteLine(); } if (worlds == null) { if (includeMetadata) { writer.WriteLine("# No world data available"); } return; } // Allegiances if (includeMetadata) { Dictionary <string, Allegiance> allegiances = new Dictionary <string, Allegiance>(); // TODO: Factor this logic out for MSEC/SectorMetaData serializers to use foreach (Allegiance alleg in worlds .Select(world => world.Allegiance) .Where(code => !allegiances.ContainsKey(code)) .Select(code => GetAllegiance(code)) .Where(alleg => alleg != null) .Distinct() .OrderBy(alleg => alleg.Code)) { writer.WriteLine("# Alleg: {0}: \"{1}\"", alleg.Code, alleg.Name); } writer.WriteLine(); } // Worlds worlds.Serialize(writer, mediaType, includeHeader, filter); }