using System.Diagnostics; using EarthQuake.Core.TopoJson; using LibTessDotNet; using MapDataGenerator; using Newtonsoft.Json; using SkiaSharp; using Transform = EarthQuake.Core.TopoJson.Transform; Console.WriteLine("----Map Data Generator v1.1----"); Console.WriteLine("(c) 2024 Okayu Group All Rights Reserved. [MIT License]"); Console.WriteLine("This program is a part of the OGSP (OkayuGroup Seismometer Project) / EarthQuake Project."); Console.WriteLine("Contact: https://github.com/OkayuGroup"); Console.WriteLine(); Console.WriteLine("What would you like to do?"); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("1. Generate a data for features in Japan."); Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("2. Generate a data for features around the world."); Console.ForegroundColor = ConsoleColor.Blue; Console.WriteLine("3. Inspect the messagepack data."); Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("4. Exit."); Console.ResetColor(); Console.Write("Enter number [1, 2, 3, 4]: "); if (!int.TryParse(Console.ReadLine(), out var b)) { Console.WriteLine("Invalid input."); return; } switch (b) { case 1: GenerateTopoJson(); break; case 2: GenerateWorld(); break; case 3: Display(); break; case 4: Console.WriteLine("Exiting..."); Console.WriteLine("Goodbye!"); return; default: Console.WriteLine("Invalid input."); break; } return; void GenerateTopoJson() { Console.WriteLine("Please enter the name of the topojson file you want to load."); Console.Write("File full-path: "); TopoJson? topo; try { string? path; if ((path = Console.ReadLine()) is null) { Console.WriteLine("Invalid input."); return; } Console.WriteLine("Loading the file... This may take a while."); topo = JsonConvert.DeserializeObject<TopoJson>(File.ReadAllText(path)); if (topo == null) { Console.WriteLine("Failed to load the file."); return; } } catch (FileNotFoundException) { Console.WriteLine("The specified file was not found."); return; } catch (JsonException) { Console.WriteLine("Failed to load the file."); return; } catch (Exception e) { Console.WriteLine(e.Message); return; } Console.WriteLine("Contents loaded"); Console.WriteLine($"Layers found: \n{string.Join("\n", topo.Objects.Keys)}]"); Console.WriteLine("1. Parsing arcs data..."); var points = topo.ParseArcs(); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("Done."); Console.ResetColor(); Console.WriteLine("2. Generate a filling layer"); Console.WriteLine("All layers will be calculated."); Dictionary<string, PolygonFeatures> filling = []; foreach (var (key, value) in topo.Objects) { Console.WriteLine($"Calculating \"{key}\" layer..."); var a = value.Geometries.Select(x => x.Arcs.SelectMany(y => y).ToArray()).ToArray(); var names = value.Geometries.Select(x => x.Properties?.Name ?? "").ToArray(); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("Done."); Console.ResetColor(); GC.Collect(); filling.Add(key, new PolygonFeatures(names, a)); } Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("All Done."); Console.ResetColor(); GC.Collect(); Console.WriteLine("4. Prepare the data for serialization."); var translate = new SKPoint((float)topo.Transform.Translate[0], (float)topo.Transform.Translate[1]); var scale = new SKPoint((float)topo.Transform.Scale[0], (float)topo.Transform.Scale[1]); var transform = new Transform(scale, translate); PolygonsSet result = new(filling, new PointsSet(points, transform)); Console.WriteLine("Data generated."); GC.Collect(); Console.WriteLine("5. Serialize the data."); Console.WriteLine("This may take a while."); Console.WriteLine("PolygonsSet => MessagePack => .mpk.lz4"); var sw = Stopwatch.StartNew(); byte[] bytes; try { bytes = Serializer.Serialize(result); } catch (Exception e) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("Failed to serialize the data."); Console.WriteLine("The data may be too large or the data structure may be incorrect."); Console.WriteLine(e); return; } sw.Stop(); Console.WriteLine($"Took {sw.ElapsedMilliseconds}ms to serialize the data, and the data size is {bytes.LongLength / 1024}KB. (Compressed)"); Console.WriteLine("6. Deserialize the data to check the data integrity."); // デシリアライズしてデータの整合性を確認 sw.Restart(); try { var deserialized = Serializer.Deserialize<PolygonsSet>(bytes); sw.Stop(); // テストポイントです _ = deserialized; Console.WriteLine( $"Took {sw.ElapsedMilliseconds}ms to deserialize the data."); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("Data integrity check passed."); Console.ResetColor(); #if DEBUG Console.WriteLine("await 5 seconds..."); Thread.Sleep(5000); #endif } catch (Exception e) { // デシリアライズに失敗するのはおかしい Console.ForegroundColor = ConsoleColor.Magenta; Console.WriteLine("[Fatal] Failed to deserialize the generated data."); Console.WriteLine("If you often encounter this error, please report it to the developer."); Console.WriteLine(e.Message); Console.WriteLine(e.StackTrace); Console.ResetColor(); return; } Console.WriteLine("7. Save the data to a file."); Console.WriteLine("Using default file path."); File.WriteAllBytes("japan.mpk.lz4", bytes); // 保存したファイルの完全パスを表示 Console.WriteLine($"The data was saved to: {Path.GetFullPath("japan.mpk.lz4")}"); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("All done successfully! The program will exit."); Console.ResetColor(); } void GenerateWorld() { Console.WriteLine("Please enter the name of the geojson file you want to load."); Console.Write("File full-path: "); GeoJson? geoJson; try { string? path; if ((path = Console.ReadLine()) is null) { Console.WriteLine("Invalid input."); return; } geoJson = JsonConvert.DeserializeObject<GeoJson>(File.ReadAllText(path)); Console.WriteLine("Loading the file... This may take a while."); if (geoJson is null) { Console.WriteLine("Failed to load the file."); return; } } catch (FileNotFoundException) { Console.WriteLine("The specified file was not found."); return; } catch (JsonException) { Console.WriteLine("Failed to load the file."); return; } catch (Exception e) { Console.WriteLine(e.Message); return; } Console.WriteLine("Contents loaded"); Console.WriteLine("1. Generate polygons from the GeoJson data."); Tess tess = new(); foreach (var feature in geoJson.Features) { for (var i = 0; i < feature.Geometry?.Coordinates.Length; i++) { feature.Geometry?.AddVertex(tess, i); } } tess.Tessellate(WindingRule.Positive); var points = new SKPoint[tess.ElementCount * 3]; for (var j = 0; j < points.Length; j++) { points[j] = new SKPoint(tess.Vertices[tess.Elements[j]].Position.X, tess.Vertices[tess.Elements[j]].Position.Y); } Console.WriteLine("Done."); Console.WriteLine("2. Generate borders from the GeoJson data."); /*var borders = geoJson.Features.SelectMany(x => x.Geometry!.Coordinates.Select(polygon => polygon.SelectMany(border => border).Select(p => new SKPoint((float)p[0], (float)p[1])).ToArray()).ToArray()).ToArray();*/ Console.WriteLine("This is not implemented yet because the data is not used in the current App."); Console.WriteLine("Done."); Console.WriteLine("3. Save the data to a file."); Console.WriteLine("Using default file path."); var data = new WorldPolygonSet(points); var bytes = Serializer.Serialize(data); File.WriteAllBytes("world.mpk.lz4", bytes); Console.WriteLine($"The data was saved to: {Path.GetFullPath("world.mpk.lz4")}"); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("All done successfully! The program will exit."); } void Display() { Console.WriteLine("Please enter the name of the data file you want to load."); Console.Write("File full-path: "); string? path; if ((path = Console.ReadLine()) is null) { Console.WriteLine("Invalid input."); return; } try { var polygonsSet = Serializer.Deserialize<PolygonsSet>(File.ReadAllBytes(path)); Console.WriteLine("Data loaded. Display with json format."); var json = JsonConvert.SerializeObject(polygonsSet); Console.WriteLine(json); } catch (Exception e) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("Failed to load the file."); Console.WriteLine(e.Message); Console.WriteLine(e.StackTrace); Console.WriteLine("The data file may be corrupted or not formatted correctly."); Console.ResetColor(); Console.WriteLine("The file may can display in json format."); Console.Write("Do you want to display the file in json format? [Y/n]: "); if (Console.ReadLine() is "y" or "Y" or "") { try { var json = JsonConvert.SerializeObject(Serializer.Deserialize<object>(File.ReadAllBytes(path))); Console.WriteLine(json[..Math.Min(json.Length, 10000)]); } catch (Exception) { Console.WriteLine("Failed to load the file."); } } } } internal static class TopoJsonGenerator { private static int _windowWidth = -1; private static string _windowResetString = ""; /// <summary> /// Parse the arcs data from the TopoJson data. /// </summary> /// <param name="topo"></param> /// <param name="detailLevels"></param> internal static IntPoint[][] ParseArcs(this TopoJson topo) { var total = topo.Arcs.Length; var count = 0; var detail = new IntPoint[topo.Arcs.Length][]; for (var i2 = 0; i2 < topo.Arcs.Length; i2++) { var arc = topo.Arcs[i2]; List<IntPoint> points = []; int x = arc[0][0], y = arc[0][1]; points.Add(new IntPoint(x, y)); for (var i = 1; i < arc.Length; i++) { var coord = arc[i]; x += coord[0]; y += coord[1]; points.Add(new IntPoint(x, y)); } detail[i2] = points.ToArray(); count++; if (count % 1000 == 0) ProgressBar(count, total, $"Calculating arcs data..."); } return detail; } private static void ProgressBar(int current, int total, string message) { if (Console.WindowWidth != _windowWidth) { _windowWidth = Console.WindowWidth; _windowResetString = new string(' ', _windowWidth); } string[] bar = ["|", "/", "-", "\\"]; Console.SetCursorPosition(0, Console.CursorTop); Console.Write(_windowResetString); Console.SetCursorPosition(0, Console.CursorTop); Console.Write(bar[current / 23 % 4]); Console.Write($" {current}/{total} ({current * 100.0 / total:0.00}%) "); Console.Write(message); Console.CursorVisible = true; } }